mailtea-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/LICENSE +21 -0
- package/README.md +165 -0
- package/bin/mailtea-mcp.mjs +4 -0
- package/dist/chunk-NEG2GOMD.js +4034 -0
- package/dist/index.d.ts +2313 -0
- package/dist/index.js +12 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +50 -0
- package/package.json +56 -0
|
@@ -0,0 +1,4034 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_API_BASE_URL = "https://api.mailtea.app";
|
|
3
|
+
var MCP_TOOLS = [
|
|
4
|
+
{
|
|
5
|
+
name: "auth.me",
|
|
6
|
+
description: "Return auth identity for current token",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "issue.create_draft",
|
|
14
|
+
description: "Create a marketing email draft. Set kind to 'newsletter' (default) for recurring content that can publish to the public site, or 'broadcast' for a one-time email-only send (promotion, launch, announcement). Provide content one of three ways: templateId (seed from a published server template \u2014 use template.list/template.get to find one \u2014 with {{variables}} substituted), contentHtml (raw HTML), or contentSpec (json-render spec). Spec is recommended for AI agents \u2014 use components: Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown, MailteaHeader, MailteaFooter, MailteaSpacer, MailteaContentBlock.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
publicationId: { type: "string" },
|
|
19
|
+
title: { type: "string" },
|
|
20
|
+
kind: { type: "string", enum: ["newsletter", "broadcast"], description: "Email kind. 'newsletter' (default) can publish to the public site; 'broadcast' is one-time email-only." },
|
|
21
|
+
templateId: { type: "string", description: "Seed the draft from a published server template (see template.list). Takes precedence over contentHtml/contentSpec." },
|
|
22
|
+
variables: { type: "object", description: "Key/value map substituted into the template's {{variable}} placeholders when templateId is set." },
|
|
23
|
+
contentHtml: { type: "string", description: "Raw HTML content (use this OR contentSpec OR templateId)" },
|
|
24
|
+
contentSpec: {
|
|
25
|
+
type: "object",
|
|
26
|
+
description: "json-render spec (flat element map). Use this OR contentHtml OR templateId. Properties: root (string, element key), elements (record of {type, props?, children?})",
|
|
27
|
+
properties: {
|
|
28
|
+
root: { type: "string" },
|
|
29
|
+
elements: { type: "object" }
|
|
30
|
+
},
|
|
31
|
+
required: ["root", "elements"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ["publicationId", "title"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "issue.get_editor",
|
|
39
|
+
description: "Load issue editor payload for a draft or scheduled issue",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
issueId: { type: "string" }
|
|
44
|
+
},
|
|
45
|
+
required: ["issueId"]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "issue.update_draft",
|
|
50
|
+
description: "Update an existing draft issue. Provide contentHtml (raw HTML) or contentSpec (json-render spec).",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
issueId: { type: "string" },
|
|
55
|
+
title: { type: "string" },
|
|
56
|
+
contentHtml: { type: "string", description: "Raw HTML content (use this OR contentSpec)" },
|
|
57
|
+
contentSpec: {
|
|
58
|
+
type: "object",
|
|
59
|
+
description: "json-render spec (flat element map). Use this OR contentHtml.",
|
|
60
|
+
properties: {
|
|
61
|
+
root: { type: "string" },
|
|
62
|
+
elements: { type: "object" }
|
|
63
|
+
},
|
|
64
|
+
required: ["root", "elements"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
required: ["issueId", "title"]
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "issue.remove_draft",
|
|
72
|
+
description: "Delete an existing draft issue",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
issueId: { type: "string" }
|
|
77
|
+
},
|
|
78
|
+
required: ["issueId"]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "issue.list_recent",
|
|
83
|
+
description: "List recent issues for a publication",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
publicationId: { type: "string" },
|
|
88
|
+
limit: { type: "number" }
|
|
89
|
+
},
|
|
90
|
+
required: ["publicationId"]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "publication.list",
|
|
95
|
+
description: "List publication workspaces available to current user/token",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "publication.create",
|
|
103
|
+
description: "Create a publication workspace and attach owner membership",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
publicationId: { type: "string" },
|
|
108
|
+
name: { type: "string" },
|
|
109
|
+
timezone: { type: "string" }
|
|
110
|
+
},
|
|
111
|
+
required: ["name"]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "publication.domain_list",
|
|
116
|
+
description: "List custom domains connected to a publication",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
publicationId: { type: "string" },
|
|
121
|
+
limit: { type: "number" }
|
|
122
|
+
},
|
|
123
|
+
required: ["publicationId"]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "publication.domain_upsert",
|
|
128
|
+
description: "Create or update a publication custom domain",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
publicationId: { type: "string" },
|
|
133
|
+
host: { type: "string" },
|
|
134
|
+
isPrimary: { type: "boolean" },
|
|
135
|
+
proxyTarget: { type: "string" }
|
|
136
|
+
},
|
|
137
|
+
required: ["publicationId", "host"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "publication.domain_verify",
|
|
142
|
+
description: "Mark a publication domain as verified",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
publicationId: { type: "string" },
|
|
147
|
+
domainId: { type: "string" },
|
|
148
|
+
verificationValue: { type: "string" }
|
|
149
|
+
},
|
|
150
|
+
required: ["publicationId", "domainId"]
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "publication.domain_set_primary",
|
|
155
|
+
description: "Set an existing publication domain as primary",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
publicationId: { type: "string" },
|
|
160
|
+
domainId: { type: "string" }
|
|
161
|
+
},
|
|
162
|
+
required: ["publicationId", "domainId"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "publication.domain_remove",
|
|
167
|
+
description: "Remove a publication custom domain",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
publicationId: { type: "string" },
|
|
172
|
+
domainId: { type: "string" }
|
|
173
|
+
},
|
|
174
|
+
required: ["publicationId", "domainId"]
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "publication.domain_traefik_preview",
|
|
179
|
+
description: "Generate Traefik dynamic-file preview for publication domains",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
publicationId: { type: "string" }
|
|
184
|
+
},
|
|
185
|
+
required: ["publicationId"]
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "contact.list",
|
|
190
|
+
description: "List contacts for a publication",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
publicationId: { type: "string" },
|
|
195
|
+
status: {
|
|
196
|
+
type: "string",
|
|
197
|
+
enum: ["active", "unsubscribed", "suppressed"]
|
|
198
|
+
},
|
|
199
|
+
query: { type: "string" },
|
|
200
|
+
limit: { type: "number" }
|
|
201
|
+
},
|
|
202
|
+
required: ["publicationId"]
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "contact.upsert",
|
|
207
|
+
description: "Create or reactivate a contact by email",
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: "object",
|
|
210
|
+
properties: {
|
|
211
|
+
publicationId: { type: "string" },
|
|
212
|
+
email: { type: "string" },
|
|
213
|
+
referrerContactId: { type: "string" }
|
|
214
|
+
},
|
|
215
|
+
required: ["publicationId", "email"]
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "contact.set_status",
|
|
220
|
+
description: "Set contact status (active, unsubscribed, suppressed)",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
publicationId: { type: "string" },
|
|
225
|
+
contactId: { type: "string" },
|
|
226
|
+
status: {
|
|
227
|
+
type: "string",
|
|
228
|
+
enum: ["active", "unsubscribed", "suppressed"]
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
required: ["publicationId", "contactId", "status"]
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "contact.import_csv",
|
|
236
|
+
description: "Import contacts from CSV text payload",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
publicationId: { type: "string" },
|
|
241
|
+
csvText: { type: "string" }
|
|
242
|
+
},
|
|
243
|
+
required: ["publicationId", "csvText"]
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "contact.referral_summary",
|
|
248
|
+
description: "Load referral conversion leaderboard for a publication",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
publicationId: { type: "string" },
|
|
253
|
+
limit: { type: "number" }
|
|
254
|
+
},
|
|
255
|
+
required: ["publicationId"]
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "contact.referral_milestones",
|
|
260
|
+
description: "List referral milestone rules for a publication",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
publicationId: { type: "string" },
|
|
265
|
+
limit: { type: "number" }
|
|
266
|
+
},
|
|
267
|
+
required: ["publicationId"]
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "contact.referral_milestone_upsert",
|
|
272
|
+
description: "Create or update a referral milestone rule",
|
|
273
|
+
inputSchema: {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties: {
|
|
276
|
+
publicationId: { type: "string" },
|
|
277
|
+
milestoneId: { type: "string" },
|
|
278
|
+
title: { type: "string" },
|
|
279
|
+
description: { type: "string" },
|
|
280
|
+
referralCount: { type: "number" }
|
|
281
|
+
},
|
|
282
|
+
required: ["publicationId", "title", "referralCount"]
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "contact.referral_milestone_remove",
|
|
287
|
+
description: "Delete a referral milestone rule",
|
|
288
|
+
inputSchema: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
publicationId: { type: "string" },
|
|
292
|
+
milestoneId: { type: "string" }
|
|
293
|
+
},
|
|
294
|
+
required: ["publicationId", "milestoneId"]
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "contact.referral_rewards",
|
|
299
|
+
description: "List awarded referral rewards for a publication",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
publicationId: { type: "string" },
|
|
304
|
+
contactId: { type: "string" },
|
|
305
|
+
limit: { type: "number" }
|
|
306
|
+
},
|
|
307
|
+
required: ["publicationId"]
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "monetize.offer_list",
|
|
312
|
+
description: "List sponsor offers for a publication",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
publicationId: { type: "string" },
|
|
317
|
+
status: {
|
|
318
|
+
type: "string",
|
|
319
|
+
enum: ["draft", "active", "paused", "archived"]
|
|
320
|
+
},
|
|
321
|
+
limit: { type: "number" }
|
|
322
|
+
},
|
|
323
|
+
required: ["publicationId"]
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "monetize.offer_upsert",
|
|
328
|
+
description: "Create or update a sponsor offer",
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
publicationId: { type: "string" },
|
|
333
|
+
offerId: { type: "string" },
|
|
334
|
+
title: { type: "string" },
|
|
335
|
+
sponsorName: { type: "string" },
|
|
336
|
+
description: { type: "string" },
|
|
337
|
+
pricingModel: {
|
|
338
|
+
type: "string",
|
|
339
|
+
enum: ["flat", "cpm"]
|
|
340
|
+
},
|
|
341
|
+
rateCents: { type: "number" },
|
|
342
|
+
estimatedPlacements: { type: "number" },
|
|
343
|
+
status: {
|
|
344
|
+
type: "string",
|
|
345
|
+
enum: ["draft", "active", "paused", "archived"]
|
|
346
|
+
},
|
|
347
|
+
startsAt: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "Optional ISO start timestamp."
|
|
350
|
+
},
|
|
351
|
+
endsAt: {
|
|
352
|
+
type: "string",
|
|
353
|
+
description: "Optional ISO end timestamp."
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
required: ["publicationId", "title", "sponsorName", "pricingModel", "rateCents"]
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "monetize.offer_remove",
|
|
361
|
+
description: "Delete a sponsor offer",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
type: "object",
|
|
364
|
+
properties: {
|
|
365
|
+
publicationId: { type: "string" },
|
|
366
|
+
offerId: { type: "string" }
|
|
367
|
+
},
|
|
368
|
+
required: ["publicationId", "offerId"]
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: "section.list",
|
|
373
|
+
description: "List reusable editor sections for a publication",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
publicationId: { type: "string" },
|
|
378
|
+
limit: { type: "number" }
|
|
379
|
+
},
|
|
380
|
+
required: ["publicationId"]
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "section.catalog",
|
|
385
|
+
description: "List shared marketplace section packs",
|
|
386
|
+
inputSchema: {
|
|
387
|
+
type: "object",
|
|
388
|
+
properties: {
|
|
389
|
+
publicationId: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Optional publication id to include custom packs"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "section.pack_create",
|
|
398
|
+
description: "Create a custom marketplace pack for a publication",
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
publicationId: { type: "string" },
|
|
403
|
+
title: { type: "string" },
|
|
404
|
+
description: { type: "string" },
|
|
405
|
+
styleProfile: {
|
|
406
|
+
type: "object",
|
|
407
|
+
description: "Optional React Email style profile for this template pack."
|
|
408
|
+
},
|
|
409
|
+
sections: {
|
|
410
|
+
type: "array",
|
|
411
|
+
items: {
|
|
412
|
+
type: "object",
|
|
413
|
+
properties: {
|
|
414
|
+
name: { type: "string" },
|
|
415
|
+
contentJson: {
|
|
416
|
+
type: "array",
|
|
417
|
+
items: { type: "object" },
|
|
418
|
+
minItems: 1
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
required: ["name", "contentJson"]
|
|
422
|
+
},
|
|
423
|
+
minItems: 1
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
required: ["publicationId", "title", "sections"]
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "section.pack_update",
|
|
431
|
+
description: "Update a custom marketplace pack",
|
|
432
|
+
inputSchema: {
|
|
433
|
+
type: "object",
|
|
434
|
+
properties: {
|
|
435
|
+
publicationId: { type: "string" },
|
|
436
|
+
packId: { type: "string" },
|
|
437
|
+
title: { type: "string" },
|
|
438
|
+
description: { type: "string" },
|
|
439
|
+
styleProfile: {
|
|
440
|
+
type: "object",
|
|
441
|
+
description: "Optional React Email style profile for this template pack."
|
|
442
|
+
},
|
|
443
|
+
sections: {
|
|
444
|
+
type: "array",
|
|
445
|
+
items: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
name: { type: "string" },
|
|
449
|
+
contentJson: {
|
|
450
|
+
type: "array",
|
|
451
|
+
items: { type: "object" },
|
|
452
|
+
minItems: 1
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
required: ["name", "contentJson"]
|
|
456
|
+
},
|
|
457
|
+
minItems: 1
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
required: ["publicationId", "packId", "title", "sections"]
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "section.pack_remove",
|
|
465
|
+
description: "Delete a custom marketplace pack",
|
|
466
|
+
inputSchema: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {
|
|
469
|
+
publicationId: { type: "string" },
|
|
470
|
+
packId: { type: "string" }
|
|
471
|
+
},
|
|
472
|
+
required: ["publicationId", "packId"]
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "section.pack_revisions",
|
|
477
|
+
description: "List version history for a custom marketplace pack",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
publicationId: { type: "string" },
|
|
482
|
+
packId: { type: "string" },
|
|
483
|
+
limit: { type: "number" }
|
|
484
|
+
},
|
|
485
|
+
required: ["publicationId", "packId"]
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "section.pack_restore_revision",
|
|
490
|
+
description: "Restore a custom marketplace pack to a specific revision",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
publicationId: { type: "string" },
|
|
495
|
+
packId: { type: "string" },
|
|
496
|
+
revisionId: { type: "string" }
|
|
497
|
+
},
|
|
498
|
+
required: ["publicationId", "packId", "revisionId"]
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: "section.import_pack",
|
|
503
|
+
description: "Import a marketplace pack into reusable sections",
|
|
504
|
+
inputSchema: {
|
|
505
|
+
type: "object",
|
|
506
|
+
properties: {
|
|
507
|
+
publicationId: { type: "string" },
|
|
508
|
+
presetId: { type: "string" }
|
|
509
|
+
},
|
|
510
|
+
required: ["publicationId", "presetId"]
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "section.create",
|
|
515
|
+
description: "Create a reusable section snippet",
|
|
516
|
+
inputSchema: {
|
|
517
|
+
type: "object",
|
|
518
|
+
properties: {
|
|
519
|
+
publicationId: { type: "string" },
|
|
520
|
+
name: { type: "string" },
|
|
521
|
+
contentJson: {
|
|
522
|
+
type: "array",
|
|
523
|
+
items: { type: "object" },
|
|
524
|
+
minItems: 1
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
required: ["publicationId", "name", "contentJson"]
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "section.update",
|
|
532
|
+
description: "Update a reusable section snippet",
|
|
533
|
+
inputSchema: {
|
|
534
|
+
type: "object",
|
|
535
|
+
properties: {
|
|
536
|
+
sectionId: { type: "string" },
|
|
537
|
+
name: { type: "string" },
|
|
538
|
+
contentJson: {
|
|
539
|
+
type: "array",
|
|
540
|
+
items: { type: "object" },
|
|
541
|
+
minItems: 1
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
required: ["sectionId", "name", "contentJson"]
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "section.remove",
|
|
549
|
+
description: "Delete a reusable section snippet",
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
sectionId: { type: "string" }
|
|
554
|
+
},
|
|
555
|
+
required: ["sectionId"]
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "issue.preview",
|
|
560
|
+
description: "Preview newsletter HTML for an issue",
|
|
561
|
+
inputSchema: {
|
|
562
|
+
type: "object",
|
|
563
|
+
properties: {
|
|
564
|
+
issueId: { type: "string" }
|
|
565
|
+
},
|
|
566
|
+
required: ["issueId"]
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "issue.preview_draft",
|
|
571
|
+
description: "Preview newsletter HTML/text for unsaved draft content",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {
|
|
575
|
+
publicationId: { type: "string" },
|
|
576
|
+
title: { type: "string" },
|
|
577
|
+
html: { type: "string" },
|
|
578
|
+
plainText: { type: "string" },
|
|
579
|
+
styleProfile: {
|
|
580
|
+
type: "object",
|
|
581
|
+
description: "Optional React Email style profile overrides."
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
required: ["publicationId", "title", "html"]
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: "issue.delivery_progress",
|
|
589
|
+
description: "Fetch send progress snapshot for an issue",
|
|
590
|
+
inputSchema: {
|
|
591
|
+
type: "object",
|
|
592
|
+
properties: {
|
|
593
|
+
publicationId: { type: "string" },
|
|
594
|
+
issueId: { type: "string" }
|
|
595
|
+
},
|
|
596
|
+
required: ["publicationId", "issueId"]
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
name: "issue.wait_delivery",
|
|
601
|
+
description: "Poll issue send progress until sent/failed or timeout",
|
|
602
|
+
inputSchema: {
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {
|
|
605
|
+
publicationId: { type: "string" },
|
|
606
|
+
issueId: { type: "string" },
|
|
607
|
+
timeoutMs: {
|
|
608
|
+
type: "number",
|
|
609
|
+
description: "Optional timeout in milliseconds. Defaults to 60000."
|
|
610
|
+
},
|
|
611
|
+
pollIntervalMs: {
|
|
612
|
+
type: "number",
|
|
613
|
+
description: "Optional polling interval in milliseconds. Defaults to 2000."
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
required: ["publicationId", "issueId"]
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: "analytics.poll_results",
|
|
621
|
+
description: "Fetch poll vote totals for a specific issue",
|
|
622
|
+
inputSchema: {
|
|
623
|
+
type: "object",
|
|
624
|
+
properties: {
|
|
625
|
+
publicationId: { type: "string" },
|
|
626
|
+
issueId: { type: "string" }
|
|
627
|
+
},
|
|
628
|
+
required: ["publicationId", "issueId"]
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "analytics.issue_performance",
|
|
633
|
+
description: "Fetch open/click delivery analytics for an issue",
|
|
634
|
+
inputSchema: {
|
|
635
|
+
type: "object",
|
|
636
|
+
properties: {
|
|
637
|
+
publicationId: { type: "string" },
|
|
638
|
+
issueId: { type: "string" },
|
|
639
|
+
range: {
|
|
640
|
+
type: "string",
|
|
641
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
642
|
+
description: "Analytics window. Defaults to all."
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
required: ["publicationId", "issueId"]
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: "analytics.issue_trend",
|
|
650
|
+
description: "Fetch daily open/click trend buckets for an issue",
|
|
651
|
+
inputSchema: {
|
|
652
|
+
type: "object",
|
|
653
|
+
properties: {
|
|
654
|
+
publicationId: { type: "string" },
|
|
655
|
+
issueId: { type: "string" },
|
|
656
|
+
range: {
|
|
657
|
+
type: "string",
|
|
658
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
659
|
+
description: "Analytics window. Defaults to all."
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
required: ["publicationId", "issueId"]
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "analytics.latest_summary",
|
|
667
|
+
description: "Fetch latest issue analytics snapshot for a publication",
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: "object",
|
|
670
|
+
properties: {
|
|
671
|
+
publicationId: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "Optional publication id. Falls back to MAILTEA_PUBLICATION_ID."
|
|
674
|
+
},
|
|
675
|
+
range: {
|
|
676
|
+
type: "string",
|
|
677
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
678
|
+
description: "Analytics window. Defaults to 7d."
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: "analytics.issue_export_csv",
|
|
685
|
+
description: "Export issue performance and poll analytics as CSV",
|
|
686
|
+
inputSchema: {
|
|
687
|
+
type: "object",
|
|
688
|
+
properties: {
|
|
689
|
+
publicationId: { type: "string" },
|
|
690
|
+
issueId: { type: "string" },
|
|
691
|
+
range: {
|
|
692
|
+
type: "string",
|
|
693
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
694
|
+
description: "Analytics window. Defaults to all."
|
|
695
|
+
},
|
|
696
|
+
exportType: {
|
|
697
|
+
type: "string",
|
|
698
|
+
enum: ["combined", "performance", "polls"],
|
|
699
|
+
description: "CSV type. Defaults to combined."
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
required: ["publicationId", "issueId"]
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: "analytics.issue_export_performance_csv",
|
|
707
|
+
description: "Export issue performance-only analytics as CSV",
|
|
708
|
+
inputSchema: {
|
|
709
|
+
type: "object",
|
|
710
|
+
properties: {
|
|
711
|
+
publicationId: { type: "string" },
|
|
712
|
+
issueId: { type: "string" },
|
|
713
|
+
range: {
|
|
714
|
+
type: "string",
|
|
715
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
716
|
+
description: "Analytics window. Defaults to all."
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
required: ["publicationId", "issueId"]
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "analytics.issue_export_polls_csv",
|
|
724
|
+
description: "Export issue poll-only analytics as CSV",
|
|
725
|
+
inputSchema: {
|
|
726
|
+
type: "object",
|
|
727
|
+
properties: {
|
|
728
|
+
publicationId: { type: "string" },
|
|
729
|
+
issueId: { type: "string" },
|
|
730
|
+
range: {
|
|
731
|
+
type: "string",
|
|
732
|
+
enum: ["24h", "7d", "30d", "all"],
|
|
733
|
+
description: "Analytics window. Defaults to all."
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
required: ["publicationId", "issueId"]
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: "issue.schedule",
|
|
741
|
+
description: "Schedule an issue for delivery",
|
|
742
|
+
inputSchema: {
|
|
743
|
+
type: "object",
|
|
744
|
+
properties: {
|
|
745
|
+
issueId: { type: "string" },
|
|
746
|
+
scheduledFor: {
|
|
747
|
+
type: "string",
|
|
748
|
+
description: "ISO timestamp (optional). Defaults to now."
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
required: ["issueId"]
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
name: "issue.unschedule",
|
|
756
|
+
description: "Cancel an issue schedule and move it back to draft",
|
|
757
|
+
inputSchema: {
|
|
758
|
+
type: "object",
|
|
759
|
+
properties: {
|
|
760
|
+
issueId: { type: "string" }
|
|
761
|
+
},
|
|
762
|
+
required: ["issueId"]
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "issue.publish_to_web",
|
|
767
|
+
description: "Publish an issue to the publication's public website, making it readable on the web (typically a sent issue).",
|
|
768
|
+
inputSchema: {
|
|
769
|
+
type: "object",
|
|
770
|
+
properties: { issueId: { type: "string" } },
|
|
771
|
+
required: ["issueId"]
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "issue.unpublish_from_web",
|
|
776
|
+
description: "Remove an issue from the publication's public website.",
|
|
777
|
+
inputSchema: {
|
|
778
|
+
type: "object",
|
|
779
|
+
properties: { issueId: { type: "string" } },
|
|
780
|
+
required: ["issueId"]
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
name: "issue.send_now",
|
|
785
|
+
description: "Send an issue immediately",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
issueId: { type: "string" }
|
|
790
|
+
},
|
|
791
|
+
required: ["issueId"]
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "issue.send_and_wait",
|
|
796
|
+
description: "Send an issue now and poll until sent/failed or timeout",
|
|
797
|
+
inputSchema: {
|
|
798
|
+
type: "object",
|
|
799
|
+
properties: {
|
|
800
|
+
issueId: { type: "string" },
|
|
801
|
+
timeoutMs: {
|
|
802
|
+
type: "number",
|
|
803
|
+
description: "Optional timeout in milliseconds. Defaults to 60000."
|
|
804
|
+
},
|
|
805
|
+
pollIntervalMs: {
|
|
806
|
+
type: "number",
|
|
807
|
+
description: "Optional polling interval in milliseconds. Defaults to 2000."
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
required: ["issueId"]
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
name: "ai.generate_draft",
|
|
815
|
+
description: "Generate a draft from a prompt",
|
|
816
|
+
inputSchema: {
|
|
817
|
+
type: "object",
|
|
818
|
+
properties: {
|
|
819
|
+
publicationId: { type: "string" },
|
|
820
|
+
prompt: { type: "string" },
|
|
821
|
+
tone: {
|
|
822
|
+
type: "string",
|
|
823
|
+
enum: ["neutral", "friendly", "formal"]
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
required: ["publicationId", "prompt"]
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: "template.create",
|
|
831
|
+
description: "Create an email template. Provide html (raw HTML) or spec (json-render spec). Spec is recommended \u2014 the platform renders email-safe HTML server-side. Available spec components: Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown, MailteaHeader, MailteaFooter, MailteaSpacer, MailteaContentBlock.",
|
|
832
|
+
inputSchema: {
|
|
833
|
+
type: "object",
|
|
834
|
+
properties: {
|
|
835
|
+
publicationId: { type: "string", description: "Publication to create template in" },
|
|
836
|
+
name: { type: "string", description: "Template name (max 120 chars)" },
|
|
837
|
+
html: { type: "string", description: "Raw HTML content (use this OR spec)" },
|
|
838
|
+
spec: {
|
|
839
|
+
type: "object",
|
|
840
|
+
description: "json-render spec (flat element map). Use this OR html.",
|
|
841
|
+
properties: {
|
|
842
|
+
root: { type: "string" },
|
|
843
|
+
elements: { type: "object" }
|
|
844
|
+
},
|
|
845
|
+
required: ["root", "elements"]
|
|
846
|
+
},
|
|
847
|
+
description: { type: "string", description: "Template description (max 500 chars)" },
|
|
848
|
+
subject: { type: "string", description: "Default email subject line" },
|
|
849
|
+
variables: {
|
|
850
|
+
type: "array",
|
|
851
|
+
description: "Template variables with fallback values",
|
|
852
|
+
items: {
|
|
853
|
+
type: "object",
|
|
854
|
+
properties: {
|
|
855
|
+
key: { type: "string" },
|
|
856
|
+
type: { type: "string", enum: ["string", "number"] },
|
|
857
|
+
fallback_value: {}
|
|
858
|
+
},
|
|
859
|
+
required: ["key", "type"]
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
required: ["publicationId", "name"]
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: "template.list",
|
|
868
|
+
description: "List email templates for a publication",
|
|
869
|
+
inputSchema: {
|
|
870
|
+
type: "object",
|
|
871
|
+
properties: {
|
|
872
|
+
publicationId: { type: "string" },
|
|
873
|
+
limit: { type: "number", description: "Max results (1-100, default 20)" }
|
|
874
|
+
},
|
|
875
|
+
required: ["publicationId"]
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: "template.get",
|
|
880
|
+
description: "Get a single email template by ID, including its spec and rendered HTML",
|
|
881
|
+
inputSchema: {
|
|
882
|
+
type: "object",
|
|
883
|
+
properties: {
|
|
884
|
+
publicationId: { type: "string" },
|
|
885
|
+
templateId: { type: "string" }
|
|
886
|
+
},
|
|
887
|
+
required: ["publicationId", "templateId"]
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: "template.update",
|
|
892
|
+
description: "Update an email template. Pass only the fields to change. Providing spec re-renders email-safe HTML server-side; providing html switches the template to raw HTML.",
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: "object",
|
|
895
|
+
properties: {
|
|
896
|
+
publicationId: { type: "string" },
|
|
897
|
+
templateId: { type: "string" },
|
|
898
|
+
name: { type: "string" },
|
|
899
|
+
html: { type: "string", description: "Raw HTML content (switches the template to raw HTML)." },
|
|
900
|
+
spec: {
|
|
901
|
+
type: "object",
|
|
902
|
+
description: "json-render spec (flat element map). Re-renders email-safe HTML server-side.",
|
|
903
|
+
properties: { root: { type: "string" }, elements: { type: "object" } },
|
|
904
|
+
required: ["root", "elements"]
|
|
905
|
+
},
|
|
906
|
+
description: { type: "string" },
|
|
907
|
+
text: { type: "string", description: "Plain-text body." },
|
|
908
|
+
subject: { type: "string" },
|
|
909
|
+
from: { type: "string", description: "Default sender address." },
|
|
910
|
+
reply_to: { type: "string" },
|
|
911
|
+
variables: {
|
|
912
|
+
type: "array",
|
|
913
|
+
items: {
|
|
914
|
+
type: "object",
|
|
915
|
+
properties: {
|
|
916
|
+
key: { type: "string" },
|
|
917
|
+
type: { type: "string", enum: ["string", "number"] },
|
|
918
|
+
fallback_value: {}
|
|
919
|
+
},
|
|
920
|
+
required: ["key", "type"]
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
required: ["publicationId", "templateId"]
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
name: "template.publish",
|
|
929
|
+
description: "Publish a draft email template, making it the active version for sends.",
|
|
930
|
+
inputSchema: {
|
|
931
|
+
type: "object",
|
|
932
|
+
properties: {
|
|
933
|
+
publicationId: { type: "string" },
|
|
934
|
+
templateId: { type: "string" }
|
|
935
|
+
},
|
|
936
|
+
required: ["publicationId", "templateId"]
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: "template.duplicate",
|
|
941
|
+
description: "Duplicate an email template into a new draft copy.",
|
|
942
|
+
inputSchema: {
|
|
943
|
+
type: "object",
|
|
944
|
+
properties: {
|
|
945
|
+
publicationId: { type: "string" },
|
|
946
|
+
templateId: { type: "string" }
|
|
947
|
+
},
|
|
948
|
+
required: ["publicationId", "templateId"]
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
name: "template.delete",
|
|
953
|
+
description: "Delete an email template.",
|
|
954
|
+
inputSchema: {
|
|
955
|
+
type: "object",
|
|
956
|
+
properties: {
|
|
957
|
+
publicationId: { type: "string" },
|
|
958
|
+
templateId: { type: "string" }
|
|
959
|
+
},
|
|
960
|
+
required: ["publicationId", "templateId"]
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
name: "email.send",
|
|
965
|
+
description: "Send a transactional email to one or more specific recipients. Provide inline content (html and/or text) OR a template reference \u2014 not both. Unlike issue.send_now (which sends a newsletter to the WHOLE publication list), this delivers a one-shot email to the exact addresses in 'to'. Set scheduled_at to send later. Returns the new email's id.",
|
|
966
|
+
inputSchema: {
|
|
967
|
+
type: "object",
|
|
968
|
+
properties: {
|
|
969
|
+
from: {
|
|
970
|
+
type: "string",
|
|
971
|
+
description: "Sender, e.g. 'Acme <hello@acme.com>'. Must use a verified domain."
|
|
972
|
+
},
|
|
973
|
+
to: {
|
|
974
|
+
type: ["string", "array"],
|
|
975
|
+
items: { type: "string" },
|
|
976
|
+
description: "Recipient address, or array of up to 50 addresses."
|
|
977
|
+
},
|
|
978
|
+
subject: { type: "string", description: "Subject line (max 998 chars)." },
|
|
979
|
+
html: { type: "string", description: "HTML body. Use this OR template, not both." },
|
|
980
|
+
text: { type: "string", description: "Plain-text body." },
|
|
981
|
+
template: {
|
|
982
|
+
type: "object",
|
|
983
|
+
description: "Stored template reference to render instead of inline html.",
|
|
984
|
+
properties: {
|
|
985
|
+
id: { type: "string" },
|
|
986
|
+
variables: { type: "object", description: "Template variable values." }
|
|
987
|
+
},
|
|
988
|
+
required: ["id"]
|
|
989
|
+
},
|
|
990
|
+
cc: { type: ["string", "array"], items: { type: "string" }, description: "CC address(es)." },
|
|
991
|
+
bcc: { type: ["string", "array"], items: { type: "string" }, description: "BCC address(es)." },
|
|
992
|
+
reply_to: {
|
|
993
|
+
type: ["string", "array"],
|
|
994
|
+
items: { type: "string" },
|
|
995
|
+
description: "Reply-To address(es)."
|
|
996
|
+
},
|
|
997
|
+
scheduled_at: {
|
|
998
|
+
type: "string",
|
|
999
|
+
description: "ISO 8601 datetime to schedule the send. Omit to send immediately."
|
|
1000
|
+
},
|
|
1001
|
+
tags: {
|
|
1002
|
+
type: "array",
|
|
1003
|
+
description: "Custom tags for filtering/analytics.",
|
|
1004
|
+
items: {
|
|
1005
|
+
type: "object",
|
|
1006
|
+
properties: { name: { type: "string" }, value: { type: "string" } },
|
|
1007
|
+
required: ["name", "value"]
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
headers: { type: "object", description: "Custom email headers (string values)." },
|
|
1011
|
+
attachments: {
|
|
1012
|
+
type: "array",
|
|
1013
|
+
description: "File attachments.",
|
|
1014
|
+
items: {
|
|
1015
|
+
type: "object",
|
|
1016
|
+
properties: {
|
|
1017
|
+
filename: { type: "string" },
|
|
1018
|
+
content: { type: "string", description: "Base64-encoded file content." },
|
|
1019
|
+
content_type: { type: "string" },
|
|
1020
|
+
content_id: { type: "string", description: "For inline images (cid:)." }
|
|
1021
|
+
},
|
|
1022
|
+
required: ["filename", "content"]
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
required: ["from", "to", "subject"]
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
name: "email.batch",
|
|
1031
|
+
description: "Send up to 100 transactional emails in one request. Each item is shaped like email.send but WITHOUT attachments or scheduled_at. Returns the ids in request order.",
|
|
1032
|
+
inputSchema: {
|
|
1033
|
+
type: "object",
|
|
1034
|
+
properties: {
|
|
1035
|
+
emails: {
|
|
1036
|
+
type: "array",
|
|
1037
|
+
description: "1-100 email objects: { from, to, subject, html?|text?|template?, cc?, bcc?, reply_to?, tags?, headers? }.",
|
|
1038
|
+
items: { type: "object" }
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
required: ["emails"]
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: "email.get",
|
|
1046
|
+
description: "Retrieve a transactional email by id with its delivery status (last_event) and tracking counters (open_count, click_count).",
|
|
1047
|
+
inputSchema: {
|
|
1048
|
+
type: "object",
|
|
1049
|
+
properties: { id: { type: "string" } },
|
|
1050
|
+
required: ["id"]
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
name: "email.reschedule",
|
|
1055
|
+
description: "Reschedule a still-scheduled transactional email to a new time.",
|
|
1056
|
+
inputSchema: {
|
|
1057
|
+
type: "object",
|
|
1058
|
+
properties: {
|
|
1059
|
+
id: { type: "string" },
|
|
1060
|
+
scheduled_at: { type: "string", description: "New ISO 8601 datetime." }
|
|
1061
|
+
},
|
|
1062
|
+
required: ["id", "scheduled_at"]
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "email.cancel",
|
|
1067
|
+
description: "Cancel a scheduled transactional email before it sends.",
|
|
1068
|
+
inputSchema: {
|
|
1069
|
+
type: "object",
|
|
1070
|
+
properties: { id: { type: "string" } },
|
|
1071
|
+
required: ["id"]
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "email.resend",
|
|
1076
|
+
description: "Resend a failed or bounced transactional email \u2014 creates a fresh copy with the same content. Only emails whose status is 'failed' or 'bounced' can be resent; others return an error.",
|
|
1077
|
+
inputSchema: {
|
|
1078
|
+
type: "object",
|
|
1079
|
+
properties: { id: { type: "string" } },
|
|
1080
|
+
required: ["id"]
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
name: "email.list",
|
|
1085
|
+
description: "List transactional emails (most recent first). Filter by status and tags; paginate with limit/offset. Returns id, status, subject, recipient per email.",
|
|
1086
|
+
inputSchema: {
|
|
1087
|
+
type: "object",
|
|
1088
|
+
properties: {
|
|
1089
|
+
status: {
|
|
1090
|
+
type: "string",
|
|
1091
|
+
enum: [
|
|
1092
|
+
"queued",
|
|
1093
|
+
"sent",
|
|
1094
|
+
"delivered",
|
|
1095
|
+
"bounced",
|
|
1096
|
+
"complained",
|
|
1097
|
+
"failed",
|
|
1098
|
+
"scheduled",
|
|
1099
|
+
"canceled"
|
|
1100
|
+
],
|
|
1101
|
+
description: "Filter by delivery status."
|
|
1102
|
+
},
|
|
1103
|
+
limit: { type: "number", description: "Max results 1-100 (default 50)." },
|
|
1104
|
+
offset: { type: "number", description: "Pagination offset (default 0)." },
|
|
1105
|
+
tag_name: { type: "string", description: "Filter by a custom tag name." },
|
|
1106
|
+
tag_value: { type: "string", description: "Filter by a custom tag value (use with tag_name)." },
|
|
1107
|
+
from_date: { type: "string", description: "ISO 8601 lower bound on created_at." },
|
|
1108
|
+
to_date: { type: "string", description: "ISO 8601 upper bound on created_at." }
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: "email.analytics",
|
|
1114
|
+
description: "Aggregate transactional email metrics over an optional date window: totals, delivered/bounced/opened/clicked counts, per-status counts, and delivery/open/click/bounce rates.",
|
|
1115
|
+
inputSchema: {
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
from_date: { type: "string", description: "ISO 8601 lower bound on created_at." },
|
|
1119
|
+
to_date: { type: "string", description: "ISO 8601 upper bound on created_at." }
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
// --- Inbound email (REST /v1/emails/inbound) -----------------------------
|
|
1124
|
+
// Only inbound_list takes a publicationId; get/attachments/reply resolve
|
|
1125
|
+
// tenancy from the inbound email id.
|
|
1126
|
+
{
|
|
1127
|
+
name: "email.inbound_list",
|
|
1128
|
+
description: "List inbound (received) emails for a publication, most recent first; paginate with limit/cursor. Only this inbound tool needs publicationId \u2014 email.inbound_get/list_attachments/get_attachment/reply resolve tenancy from the email id.",
|
|
1129
|
+
inputSchema: {
|
|
1130
|
+
type: "object",
|
|
1131
|
+
properties: {
|
|
1132
|
+
publicationId: { type: "string" },
|
|
1133
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." },
|
|
1134
|
+
cursor: { type: "string", description: "Opaque pagination cursor from a prior response's next_cursor." }
|
|
1135
|
+
},
|
|
1136
|
+
required: ["publicationId"]
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
name: "email.inbound_get",
|
|
1141
|
+
description: "Retrieve a single inbound email by id (rxemail_) \u2014 headers, html/text body, a signed download URL for the raw message, and its attachments. Tenancy is resolved from the id, so no publicationId is needed.",
|
|
1142
|
+
inputSchema: {
|
|
1143
|
+
type: "object",
|
|
1144
|
+
properties: { id: { type: "string", description: "Inbound email id (rxemail_)." } },
|
|
1145
|
+
required: ["id"]
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
name: "email.inbound_list_attachments",
|
|
1150
|
+
description: "List the attachments of an inbound email, each with a signed download URL. Tenancy is resolved from the email id, so no publicationId is needed.",
|
|
1151
|
+
inputSchema: {
|
|
1152
|
+
type: "object",
|
|
1153
|
+
properties: { id: { type: "string", description: "Inbound email id (rxemail_)." } },
|
|
1154
|
+
required: ["id"]
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: "email.inbound_get_attachment",
|
|
1159
|
+
description: "Get a single inbound-email attachment (rxatt_) with a signed download URL. Tenancy is resolved from the parent email id, so no publicationId is needed.",
|
|
1160
|
+
inputSchema: {
|
|
1161
|
+
type: "object",
|
|
1162
|
+
properties: {
|
|
1163
|
+
id: { type: "string", description: "Inbound email id (rxemail_)." },
|
|
1164
|
+
attachmentId: { type: "string", description: "Attachment id (rxatt_)." }
|
|
1165
|
+
},
|
|
1166
|
+
required: ["id", "attachmentId"]
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: "email.inbound_reply",
|
|
1171
|
+
description: "Reply to an inbound email. The reply threads automatically \u2014 In-Reply-To, References, and the To (reply target) are all derived by the server from the original, so they are NOT inputs. Provide html and/or text. Omit `from` to send from the verified domain the mail was delivered to. Tenancy is resolved from the email id (no publicationId). Returns the resulting transactional email id (txemail_) with its status.",
|
|
1172
|
+
inputSchema: {
|
|
1173
|
+
type: "object",
|
|
1174
|
+
properties: {
|
|
1175
|
+
id: { type: "string", description: "Inbound email id (rxemail_) to reply to." },
|
|
1176
|
+
from: {
|
|
1177
|
+
type: "object",
|
|
1178
|
+
description: "Sender override. Omit to send from the verified domain the original was delivered to.",
|
|
1179
|
+
properties: {
|
|
1180
|
+
email: { type: "string" },
|
|
1181
|
+
name: { type: "string" }
|
|
1182
|
+
},
|
|
1183
|
+
required: ["email"]
|
|
1184
|
+
},
|
|
1185
|
+
subject: { type: "string", description: "Defaults to the original subject with a single 'Re: ' prefix." },
|
|
1186
|
+
html: { type: "string", description: "HTML body (provide html and/or text)." },
|
|
1187
|
+
text: { type: "string", description: "Plain-text body (provide html and/or text)." },
|
|
1188
|
+
cc: { type: "array", items: { type: "string" }, description: "CC addresses." },
|
|
1189
|
+
bcc: { type: "array", items: { type: "string" }, description: "BCC addresses." },
|
|
1190
|
+
idempotency_key: { type: "string", description: "Optional key to make the reply idempotent (the Idempotency-Key header wins when both are set)." }
|
|
1191
|
+
},
|
|
1192
|
+
required: ["id"]
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
name: "issue.send_test",
|
|
1197
|
+
description: "Send a TEST copy of a newsletter draft to specific recipients (yourself/teammates) to check it before subscribers see it. Renders the issue exactly as a subscriber would receive it and delivers it as a one-shot email with a '[TEST]' subject prefix. This does NOT send to the publication's audience \u2014 use issue.send_now for the real send.",
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
type: "object",
|
|
1200
|
+
properties: {
|
|
1201
|
+
issueId: { type: "string" },
|
|
1202
|
+
recipients: {
|
|
1203
|
+
type: ["string", "array"],
|
|
1204
|
+
items: { type: "string" },
|
|
1205
|
+
description: "Test recipient address, or array of addresses."
|
|
1206
|
+
},
|
|
1207
|
+
from: {
|
|
1208
|
+
type: "string",
|
|
1209
|
+
description: "Sender, e.g. 'Acme <hello@acme.com>'. Must use a verified domain."
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
required: ["issueId", "recipients", "from"]
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
// --- Email sending domains (REST /v1/domains) ----------------------------
|
|
1216
|
+
// Provision and verify the 'from' domain that email.send / issue.send_test
|
|
1217
|
+
// require. (Distinct from publication.domain_* which manage the Traefik
|
|
1218
|
+
// website surface of the same publication.)
|
|
1219
|
+
{
|
|
1220
|
+
name: "domain.create",
|
|
1221
|
+
description: "Register an email sending domain for a publication. Returns the DNS TXT record (in 'records') the operator must add before the domain can be verified. Set purpose to 'email' (or 'both') to use it as a sending 'from' domain.",
|
|
1222
|
+
inputSchema: {
|
|
1223
|
+
type: "object",
|
|
1224
|
+
properties: {
|
|
1225
|
+
publicationId: { type: "string" },
|
|
1226
|
+
name: { type: "string", description: "Domain host, e.g. 'mail.acme.com'." },
|
|
1227
|
+
purpose: {
|
|
1228
|
+
type: "string",
|
|
1229
|
+
enum: ["email", "site", "both"],
|
|
1230
|
+
description: "Use 'email' or 'both' for a sending domain. Defaults to 'site'."
|
|
1231
|
+
},
|
|
1232
|
+
is_primary: { type: "boolean" }
|
|
1233
|
+
},
|
|
1234
|
+
required: ["publicationId", "name"]
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
name: "domain.list",
|
|
1239
|
+
description: "List email/site domains for a publication.",
|
|
1240
|
+
inputSchema: {
|
|
1241
|
+
type: "object",
|
|
1242
|
+
properties: {
|
|
1243
|
+
publicationId: { type: "string" },
|
|
1244
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." }
|
|
1245
|
+
},
|
|
1246
|
+
required: ["publicationId"]
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
name: "domain.get",
|
|
1251
|
+
description: "Get a single domain including the DNS 'records' to add for verification.",
|
|
1252
|
+
inputSchema: {
|
|
1253
|
+
type: "object",
|
|
1254
|
+
properties: {
|
|
1255
|
+
publicationId: { type: "string" },
|
|
1256
|
+
domainId: { type: "string" }
|
|
1257
|
+
},
|
|
1258
|
+
required: ["publicationId", "domainId"]
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
name: "domain.verify",
|
|
1263
|
+
description: "Verify a domain by checking its DNS TXT record. On success the domain's status becomes 'verified' and it can be used as a sending 'from' domain.",
|
|
1264
|
+
inputSchema: {
|
|
1265
|
+
type: "object",
|
|
1266
|
+
properties: {
|
|
1267
|
+
publicationId: { type: "string" },
|
|
1268
|
+
domainId: { type: "string" }
|
|
1269
|
+
},
|
|
1270
|
+
required: ["publicationId", "domainId"]
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
name: "domain.update",
|
|
1275
|
+
description: "Update a domain's purpose (email/site/both) or primary flag.",
|
|
1276
|
+
inputSchema: {
|
|
1277
|
+
type: "object",
|
|
1278
|
+
properties: {
|
|
1279
|
+
publicationId: { type: "string" },
|
|
1280
|
+
domainId: { type: "string" },
|
|
1281
|
+
purpose: { type: "string", enum: ["email", "site", "both"] },
|
|
1282
|
+
is_primary: { type: "boolean" }
|
|
1283
|
+
},
|
|
1284
|
+
required: ["publicationId", "domainId"]
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
name: "domain.delete",
|
|
1289
|
+
description: "Remove a domain from a publication.",
|
|
1290
|
+
inputSchema: {
|
|
1291
|
+
type: "object",
|
|
1292
|
+
properties: {
|
|
1293
|
+
publicationId: { type: "string" },
|
|
1294
|
+
domainId: { type: "string" }
|
|
1295
|
+
},
|
|
1296
|
+
required: ["publicationId", "domainId"]
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
// --- Tracking sub-domains (CNAME, under a domain) -------------------------
|
|
1300
|
+
{
|
|
1301
|
+
name: "domain.tracking_create",
|
|
1302
|
+
description: "Add a tracking sub-domain (CNAME) under a domain \u2014 used to serve open-pixel/click-tracking links from your own domain. Returns the CNAME record to add, then call domain.tracking_verify.",
|
|
1303
|
+
inputSchema: {
|
|
1304
|
+
type: "object",
|
|
1305
|
+
properties: {
|
|
1306
|
+
publicationId: { type: "string" },
|
|
1307
|
+
domainId: { type: "string", description: "Parent domain ID." },
|
|
1308
|
+
subdomain: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
description: "Sub-domain label (lowercase alphanumeric and hyphens), e.g. 'links'."
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
required: ["publicationId", "domainId", "subdomain"]
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
name: "domain.tracking_list",
|
|
1318
|
+
description: "List tracking sub-domains for a domain.",
|
|
1319
|
+
inputSchema: {
|
|
1320
|
+
type: "object",
|
|
1321
|
+
properties: {
|
|
1322
|
+
publicationId: { type: "string" },
|
|
1323
|
+
domainId: { type: "string" }
|
|
1324
|
+
},
|
|
1325
|
+
required: ["publicationId", "domainId"]
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
name: "domain.tracking_verify",
|
|
1330
|
+
description: "Verify a tracking sub-domain by checking its CNAME record; status becomes 'verified'.",
|
|
1331
|
+
inputSchema: {
|
|
1332
|
+
type: "object",
|
|
1333
|
+
properties: {
|
|
1334
|
+
publicationId: { type: "string" },
|
|
1335
|
+
domainId: { type: "string" },
|
|
1336
|
+
trackingDomainId: { type: "string" }
|
|
1337
|
+
},
|
|
1338
|
+
required: ["publicationId", "domainId", "trackingDomainId"]
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: "domain.tracking_delete",
|
|
1343
|
+
description: "Remove a tracking sub-domain.",
|
|
1344
|
+
inputSchema: {
|
|
1345
|
+
type: "object",
|
|
1346
|
+
properties: {
|
|
1347
|
+
publicationId: { type: "string" },
|
|
1348
|
+
domainId: { type: "string" },
|
|
1349
|
+
trackingDomainId: { type: "string" }
|
|
1350
|
+
},
|
|
1351
|
+
required: ["publicationId", "domainId", "trackingDomainId"]
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
// --- Outbound webhooks (REST /v1/webhooks/endpoints) ----------------------
|
|
1355
|
+
{
|
|
1356
|
+
name: "webhook.create",
|
|
1357
|
+
description: "Register an outbound webhook endpoint that receives delivery/engagement events. Returns a signing_secret (shown only once) used to verify payloads.",
|
|
1358
|
+
inputSchema: {
|
|
1359
|
+
type: "object",
|
|
1360
|
+
properties: {
|
|
1361
|
+
publicationId: { type: "string" },
|
|
1362
|
+
endpoint: { type: "string", description: "HTTPS URL to deliver events to." },
|
|
1363
|
+
events: {
|
|
1364
|
+
type: "array",
|
|
1365
|
+
items: { type: "string" },
|
|
1366
|
+
description: "Event types, e.g. email.received, email.sent, email.delivered, email.bounced, email.opened, email.clicked, contact.created, contact.unsubscribed."
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
required: ["publicationId", "endpoint", "events"]
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
name: "webhook.list",
|
|
1374
|
+
description: "List outbound webhooks for a publication (signing secrets omitted).",
|
|
1375
|
+
inputSchema: {
|
|
1376
|
+
type: "object",
|
|
1377
|
+
properties: {
|
|
1378
|
+
publicationId: { type: "string" },
|
|
1379
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." }
|
|
1380
|
+
},
|
|
1381
|
+
required: ["publicationId"]
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "webhook.get",
|
|
1386
|
+
description: "Get a single outbound webhook by ID.",
|
|
1387
|
+
inputSchema: {
|
|
1388
|
+
type: "object",
|
|
1389
|
+
properties: {
|
|
1390
|
+
publicationId: { type: "string" },
|
|
1391
|
+
webhookId: { type: "string" }
|
|
1392
|
+
},
|
|
1393
|
+
required: ["publicationId", "webhookId"]
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
name: "webhook.update",
|
|
1398
|
+
description: "Update a webhook's endpoint, subscribed events, or enabled/disabled status.",
|
|
1399
|
+
inputSchema: {
|
|
1400
|
+
type: "object",
|
|
1401
|
+
properties: {
|
|
1402
|
+
publicationId: { type: "string" },
|
|
1403
|
+
webhookId: { type: "string" },
|
|
1404
|
+
endpoint: { type: "string" },
|
|
1405
|
+
events: { type: "array", items: { type: "string" } },
|
|
1406
|
+
status: { type: "string", enum: ["enabled", "disabled"] }
|
|
1407
|
+
},
|
|
1408
|
+
required: ["publicationId", "webhookId"]
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "webhook.delete",
|
|
1413
|
+
description: "Delete an outbound webhook.",
|
|
1414
|
+
inputSchema: {
|
|
1415
|
+
type: "object",
|
|
1416
|
+
properties: {
|
|
1417
|
+
publicationId: { type: "string" },
|
|
1418
|
+
webhookId: { type: "string" }
|
|
1419
|
+
},
|
|
1420
|
+
required: ["publicationId", "webhookId"]
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
// --- Audience segments (REST /v1/segments) --------------------------------
|
|
1424
|
+
{
|
|
1425
|
+
name: "segment.create",
|
|
1426
|
+
description: "Create a saved audience segment defined by a status filter and/or a query filter (filter-based, not manual membership).",
|
|
1427
|
+
inputSchema: {
|
|
1428
|
+
type: "object",
|
|
1429
|
+
properties: {
|
|
1430
|
+
publicationId: { type: "string" },
|
|
1431
|
+
name: { type: "string" },
|
|
1432
|
+
description: { type: "string" },
|
|
1433
|
+
status_filter: { type: "string", enum: ["active", "unsubscribed", "suppressed"] },
|
|
1434
|
+
query_filter: { type: "string", description: "Search/filter expression over contacts." }
|
|
1435
|
+
},
|
|
1436
|
+
required: ["publicationId", "name"]
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "segment.list",
|
|
1441
|
+
description: "List saved audience segments for a publication.",
|
|
1442
|
+
inputSchema: {
|
|
1443
|
+
type: "object",
|
|
1444
|
+
properties: {
|
|
1445
|
+
publicationId: { type: "string" },
|
|
1446
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." }
|
|
1447
|
+
},
|
|
1448
|
+
required: ["publicationId"]
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
name: "segment.get",
|
|
1453
|
+
description: "Get a single audience segment by ID.",
|
|
1454
|
+
inputSchema: {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {
|
|
1457
|
+
publicationId: { type: "string" },
|
|
1458
|
+
segmentId: { type: "string" }
|
|
1459
|
+
},
|
|
1460
|
+
required: ["publicationId", "segmentId"]
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
name: "segment.update",
|
|
1465
|
+
description: "Update an audience segment's name, description, or filters.",
|
|
1466
|
+
inputSchema: {
|
|
1467
|
+
type: "object",
|
|
1468
|
+
properties: {
|
|
1469
|
+
publicationId: { type: "string" },
|
|
1470
|
+
segmentId: { type: "string" },
|
|
1471
|
+
name: { type: "string" },
|
|
1472
|
+
description: { type: "string" },
|
|
1473
|
+
status_filter: {
|
|
1474
|
+
type: ["string", "null"],
|
|
1475
|
+
enum: ["active", "unsubscribed", "suppressed", null],
|
|
1476
|
+
description: "Filter by contact status, or null to clear it."
|
|
1477
|
+
},
|
|
1478
|
+
query_filter: {
|
|
1479
|
+
type: ["string", "null"],
|
|
1480
|
+
description: "Search/filter expression, or null to clear it."
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
required: ["publicationId", "segmentId"]
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
name: "segment.delete",
|
|
1488
|
+
description: "Delete an audience segment.",
|
|
1489
|
+
inputSchema: {
|
|
1490
|
+
type: "object",
|
|
1491
|
+
properties: {
|
|
1492
|
+
publicationId: { type: "string" },
|
|
1493
|
+
segmentId: { type: "string" }
|
|
1494
|
+
},
|
|
1495
|
+
required: ["publicationId", "segmentId"]
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
// --- Contact custom properties (REST /v1/contact-properties; team-scoped) -
|
|
1499
|
+
{
|
|
1500
|
+
name: "contact_property.create",
|
|
1501
|
+
description: "Define a custom contact property (custom field) for the team. Property values are set per-contact via contact.set_properties.",
|
|
1502
|
+
inputSchema: {
|
|
1503
|
+
type: "object",
|
|
1504
|
+
properties: {
|
|
1505
|
+
key: {
|
|
1506
|
+
type: "string",
|
|
1507
|
+
description: "Property key (starts with a letter; letters, numbers, underscores)."
|
|
1508
|
+
},
|
|
1509
|
+
type: { type: "string", enum: ["string", "number"] },
|
|
1510
|
+
fallback_value: { description: "Default value when a contact has none (matches type)." },
|
|
1511
|
+
description: { type: "string" }
|
|
1512
|
+
},
|
|
1513
|
+
required: ["key", "type"]
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
name: "contact_property.list",
|
|
1518
|
+
description: "List the team's custom contact property definitions.",
|
|
1519
|
+
inputSchema: {
|
|
1520
|
+
type: "object",
|
|
1521
|
+
properties: {
|
|
1522
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." }
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
name: "contact_property.update",
|
|
1528
|
+
description: "Update a custom contact property's fallback value or description.",
|
|
1529
|
+
inputSchema: {
|
|
1530
|
+
type: "object",
|
|
1531
|
+
properties: {
|
|
1532
|
+
propertyId: { type: "string" },
|
|
1533
|
+
fallback_value: {},
|
|
1534
|
+
description: { type: "string" }
|
|
1535
|
+
},
|
|
1536
|
+
required: ["propertyId"]
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
name: "contact_property.delete",
|
|
1541
|
+
description: "Delete a custom contact property definition.",
|
|
1542
|
+
inputSchema: {
|
|
1543
|
+
type: "object",
|
|
1544
|
+
properties: {
|
|
1545
|
+
propertyId: { type: "string" }
|
|
1546
|
+
},
|
|
1547
|
+
required: ["propertyId"]
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
// --- Contact completeness (REST GET/DELETE + tRPC property values) --------
|
|
1551
|
+
{
|
|
1552
|
+
name: "contact.get",
|
|
1553
|
+
description: "Get a single contact by its ID or email address.",
|
|
1554
|
+
inputSchema: {
|
|
1555
|
+
type: "object",
|
|
1556
|
+
properties: {
|
|
1557
|
+
publicationId: { type: "string" },
|
|
1558
|
+
idOrEmail: { type: "string", description: "Contact ID or email address." }
|
|
1559
|
+
},
|
|
1560
|
+
required: ["publicationId", "idOrEmail"]
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: "contact.delete",
|
|
1565
|
+
description: "Permanently delete a contact by its ID or email address.",
|
|
1566
|
+
inputSchema: {
|
|
1567
|
+
type: "object",
|
|
1568
|
+
properties: {
|
|
1569
|
+
publicationId: { type: "string" },
|
|
1570
|
+
idOrEmail: { type: "string", description: "Contact ID or email address." }
|
|
1571
|
+
},
|
|
1572
|
+
required: ["publicationId", "idOrEmail"]
|
|
1573
|
+
}
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
name: "contact.get_properties",
|
|
1577
|
+
description: "Read a contact's custom property values.",
|
|
1578
|
+
inputSchema: {
|
|
1579
|
+
type: "object",
|
|
1580
|
+
properties: {
|
|
1581
|
+
publicationId: { type: "string" },
|
|
1582
|
+
contactId: { type: "string" }
|
|
1583
|
+
},
|
|
1584
|
+
required: ["publicationId", "contactId"]
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
name: "contact.set_properties",
|
|
1589
|
+
description: "Set a contact's custom property values. Each value references a property by propertyId (from contact_property.list/create).",
|
|
1590
|
+
inputSchema: {
|
|
1591
|
+
type: "object",
|
|
1592
|
+
properties: {
|
|
1593
|
+
publicationId: { type: "string" },
|
|
1594
|
+
contactId: { type: "string" },
|
|
1595
|
+
values: {
|
|
1596
|
+
type: "array",
|
|
1597
|
+
items: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
properties: {
|
|
1600
|
+
propertyId: { type: "string" },
|
|
1601
|
+
value: { type: "string" }
|
|
1602
|
+
},
|
|
1603
|
+
required: ["propertyId", "value"]
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
required: ["publicationId", "contactId", "values"]
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1610
|
+
// --- Tag definitions (REST /v1/tags) -------------------------------------
|
|
1611
|
+
{
|
|
1612
|
+
name: "tag.create",
|
|
1613
|
+
description: "Create a tag definition. NOTE: tags cannot yet be assigned to individual contacts via the API \u2014 this manages tag definitions only.",
|
|
1614
|
+
inputSchema: {
|
|
1615
|
+
type: "object",
|
|
1616
|
+
properties: {
|
|
1617
|
+
publicationId: { type: "string" },
|
|
1618
|
+
name: { type: "string" },
|
|
1619
|
+
default_subscription: { type: "string", enum: ["opt_in", "opt_out"] },
|
|
1620
|
+
description: { type: "string" },
|
|
1621
|
+
visibility: { type: "string", enum: ["public", "private"] }
|
|
1622
|
+
},
|
|
1623
|
+
required: ["publicationId", "name", "default_subscription"]
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
name: "tag.list",
|
|
1628
|
+
description: "List tag definitions for a publication.",
|
|
1629
|
+
inputSchema: {
|
|
1630
|
+
type: "object",
|
|
1631
|
+
properties: {
|
|
1632
|
+
publicationId: { type: "string" },
|
|
1633
|
+
limit: { type: "number", description: "Max results (1-100, default 20)." }
|
|
1634
|
+
},
|
|
1635
|
+
required: ["publicationId"]
|
|
1636
|
+
}
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
name: "tag.update",
|
|
1640
|
+
description: "Update a tag definition's name, description, default subscription, or visibility.",
|
|
1641
|
+
inputSchema: {
|
|
1642
|
+
type: "object",
|
|
1643
|
+
properties: {
|
|
1644
|
+
publicationId: { type: "string" },
|
|
1645
|
+
tagId: { type: "string" },
|
|
1646
|
+
name: { type: "string" },
|
|
1647
|
+
description: { type: "string" },
|
|
1648
|
+
default_subscription: { type: "string", enum: ["opt_in", "opt_out"] },
|
|
1649
|
+
visibility: { type: "string", enum: ["public", "private"] }
|
|
1650
|
+
},
|
|
1651
|
+
required: ["publicationId", "tagId"]
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
name: "tag.delete",
|
|
1656
|
+
description: "Delete a tag definition.",
|
|
1657
|
+
inputSchema: {
|
|
1658
|
+
type: "object",
|
|
1659
|
+
properties: {
|
|
1660
|
+
publicationId: { type: "string" },
|
|
1661
|
+
tagId: { type: "string" }
|
|
1662
|
+
},
|
|
1663
|
+
required: ["publicationId", "tagId"]
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
// --- API keys (REST /v1/api-keys; requires settings:write) ----------------
|
|
1667
|
+
{
|
|
1668
|
+
name: "api_key.create",
|
|
1669
|
+
description: "Create an API key (PAT). Requires the calling token to hold settings:write AND every scope the new key would grant. The token value is returned ONCE \u2014 store it securely. 'full_access' grants all scopes; 'sending_access' grants issue read/write/send only.",
|
|
1670
|
+
inputSchema: {
|
|
1671
|
+
type: "object",
|
|
1672
|
+
properties: {
|
|
1673
|
+
name: { type: "string", description: "Human label for the key (max 50 chars)." },
|
|
1674
|
+
permission: {
|
|
1675
|
+
type: "string",
|
|
1676
|
+
enum: ["full_access", "sending_access"],
|
|
1677
|
+
description: "Defaults to full_access."
|
|
1678
|
+
},
|
|
1679
|
+
domain_id: { type: "string", description: "Optional publication scope for sending_access." }
|
|
1680
|
+
},
|
|
1681
|
+
required: ["name"]
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "api_key.list",
|
|
1686
|
+
description: "List the team's API keys (token values are never returned).",
|
|
1687
|
+
inputSchema: {
|
|
1688
|
+
type: "object",
|
|
1689
|
+
properties: {}
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
name: "api_key.revoke",
|
|
1694
|
+
description: "Revoke (delete) an API key by its ID.",
|
|
1695
|
+
inputSchema: {
|
|
1696
|
+
type: "object",
|
|
1697
|
+
properties: {
|
|
1698
|
+
keyId: { type: "string" }
|
|
1699
|
+
},
|
|
1700
|
+
required: ["keyId"]
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
];
|
|
1704
|
+
var MCP_RESOURCES = [
|
|
1705
|
+
{
|
|
1706
|
+
uri: "publication://current/brand-guidelines",
|
|
1707
|
+
name: "Brand Guidelines",
|
|
1708
|
+
description: "Publication writing style and visual preferences",
|
|
1709
|
+
mimeType: "application/json"
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
uri: "mailtea://capabilities",
|
|
1713
|
+
name: "Mailtea MCP Capabilities",
|
|
1714
|
+
description: "Tool and endpoint capabilities for this MCP runtime",
|
|
1715
|
+
mimeType: "application/json"
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
uri: "analytics://current/latest-summary",
|
|
1719
|
+
name: "Latest Analytics Summary",
|
|
1720
|
+
description: "Most recent newsletter engagement snapshot",
|
|
1721
|
+
mimeType: "application/json"
|
|
1722
|
+
}
|
|
1723
|
+
];
|
|
1724
|
+
var MCP_PROMPTS = [
|
|
1725
|
+
{
|
|
1726
|
+
name: "newsletter.draft_from_brief",
|
|
1727
|
+
description: "Create a newsletter draft from a short brief"
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
name: "newsletter.subject_line_pack",
|
|
1731
|
+
description: "Generate subject line variants"
|
|
1732
|
+
}
|
|
1733
|
+
];
|
|
1734
|
+
function response(id, result) {
|
|
1735
|
+
return {
|
|
1736
|
+
jsonrpc: "2.0",
|
|
1737
|
+
id,
|
|
1738
|
+
result
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
function error(id, code, message, data) {
|
|
1742
|
+
return {
|
|
1743
|
+
jsonrpc: "2.0",
|
|
1744
|
+
id,
|
|
1745
|
+
error: {
|
|
1746
|
+
code,
|
|
1747
|
+
message,
|
|
1748
|
+
data
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function asObject(value) {
|
|
1753
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1754
|
+
return {};
|
|
1755
|
+
}
|
|
1756
|
+
return value;
|
|
1757
|
+
}
|
|
1758
|
+
function asOptionalString(value) {
|
|
1759
|
+
if (typeof value !== "string") {
|
|
1760
|
+
return void 0;
|
|
1761
|
+
}
|
|
1762
|
+
const trimmed = value.trim();
|
|
1763
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1764
|
+
}
|
|
1765
|
+
function readRequiredString(args, key) {
|
|
1766
|
+
const value = asOptionalString(args[key]);
|
|
1767
|
+
if (!value) {
|
|
1768
|
+
throw new Error(`Missing required string argument: ${key}`);
|
|
1769
|
+
}
|
|
1770
|
+
return value;
|
|
1771
|
+
}
|
|
1772
|
+
function readIssueAnalyticsRange(args, key) {
|
|
1773
|
+
const value = asOptionalString(args[key]);
|
|
1774
|
+
return parseIssueAnalyticsRange(value, key);
|
|
1775
|
+
}
|
|
1776
|
+
function readIssueAnalyticsExportType(args, key) {
|
|
1777
|
+
const value = asOptionalString(args[key]);
|
|
1778
|
+
if (!value) {
|
|
1779
|
+
return void 0;
|
|
1780
|
+
}
|
|
1781
|
+
if (value === "combined" || value === "performance" || value === "polls") {
|
|
1782
|
+
return value;
|
|
1783
|
+
}
|
|
1784
|
+
throw new Error(`Argument ${key} must be one of: combined, performance, polls`);
|
|
1785
|
+
}
|
|
1786
|
+
function parseIssueAnalyticsRange(value, key) {
|
|
1787
|
+
if (!value) {
|
|
1788
|
+
return void 0;
|
|
1789
|
+
}
|
|
1790
|
+
if (value === "24h" || value === "7d" || value === "30d" || value === "all") {
|
|
1791
|
+
return value;
|
|
1792
|
+
}
|
|
1793
|
+
throw new Error(`Argument ${key} must be one of: 24h, 7d, 30d, all`);
|
|
1794
|
+
}
|
|
1795
|
+
function readContactStatus(args, key) {
|
|
1796
|
+
const value = asOptionalString(args[key]);
|
|
1797
|
+
if (!value) {
|
|
1798
|
+
return void 0;
|
|
1799
|
+
}
|
|
1800
|
+
if (value === "active" || value === "unsubscribed" || value === "suppressed") {
|
|
1801
|
+
return value;
|
|
1802
|
+
}
|
|
1803
|
+
throw new Error(`Argument ${key} must be one of: active, unsubscribed, suppressed`);
|
|
1804
|
+
}
|
|
1805
|
+
function readSponsorOfferStatus(args, key) {
|
|
1806
|
+
const value = asOptionalString(args[key]);
|
|
1807
|
+
if (!value) {
|
|
1808
|
+
return void 0;
|
|
1809
|
+
}
|
|
1810
|
+
if (value === "draft" || value === "active" || value === "paused" || value === "archived") {
|
|
1811
|
+
return value;
|
|
1812
|
+
}
|
|
1813
|
+
throw new Error(`Argument ${key} must be one of: draft, active, paused, archived`);
|
|
1814
|
+
}
|
|
1815
|
+
function readSponsorOfferPricingModel(args, key) {
|
|
1816
|
+
const value = asOptionalString(args[key]);
|
|
1817
|
+
if (!value) {
|
|
1818
|
+
return void 0;
|
|
1819
|
+
}
|
|
1820
|
+
if (value === "flat" || value === "cpm") {
|
|
1821
|
+
return value;
|
|
1822
|
+
}
|
|
1823
|
+
throw new Error(`Argument ${key} must be one of: flat, cpm`);
|
|
1824
|
+
}
|
|
1825
|
+
function readOptionalNumber(args, key) {
|
|
1826
|
+
const value = args[key];
|
|
1827
|
+
if (value === void 0) {
|
|
1828
|
+
return void 0;
|
|
1829
|
+
}
|
|
1830
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
1831
|
+
const parsed = Number(value);
|
|
1832
|
+
if (Number.isFinite(parsed)) {
|
|
1833
|
+
return parsed;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1837
|
+
throw new Error(`Argument ${key} must be a finite number`);
|
|
1838
|
+
}
|
|
1839
|
+
return value;
|
|
1840
|
+
}
|
|
1841
|
+
function sleep(ms) {
|
|
1842
|
+
return new Promise((resolve) => {
|
|
1843
|
+
setTimeout(resolve, ms);
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
function readOptionalBoolean(args, key) {
|
|
1847
|
+
const value = args[key];
|
|
1848
|
+
if (value === void 0) {
|
|
1849
|
+
return void 0;
|
|
1850
|
+
}
|
|
1851
|
+
if (typeof value !== "boolean") {
|
|
1852
|
+
throw new Error(`Argument ${key} must be a boolean`);
|
|
1853
|
+
}
|
|
1854
|
+
return value;
|
|
1855
|
+
}
|
|
1856
|
+
function readOptionalJsonObject(args, key) {
|
|
1857
|
+
const value = args[key];
|
|
1858
|
+
if (value === void 0) {
|
|
1859
|
+
return void 0;
|
|
1860
|
+
}
|
|
1861
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1862
|
+
throw new Error(`Argument ${key} must be a JSON object`);
|
|
1863
|
+
}
|
|
1864
|
+
return value;
|
|
1865
|
+
}
|
|
1866
|
+
function readRequiredJsonObjectArray(args, key) {
|
|
1867
|
+
const value = args[key];
|
|
1868
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1869
|
+
throw new Error(`Argument ${key} must be a non-empty array of objects`);
|
|
1870
|
+
}
|
|
1871
|
+
return value.map((item) => {
|
|
1872
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
1873
|
+
throw new Error(`Argument ${key} must be a non-empty array of objects`);
|
|
1874
|
+
}
|
|
1875
|
+
return item;
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
function readRequiredPackSections(args, key) {
|
|
1879
|
+
const rows = readRequiredJsonObjectArray(args, key);
|
|
1880
|
+
return rows.map((row) => {
|
|
1881
|
+
const name = asOptionalString(row.name);
|
|
1882
|
+
if (!name) {
|
|
1883
|
+
throw new Error(`Argument ${key} item requires a non-empty name`);
|
|
1884
|
+
}
|
|
1885
|
+
const contentJson = row.contentJson;
|
|
1886
|
+
if (!Array.isArray(contentJson) || contentJson.length === 0) {
|
|
1887
|
+
throw new Error(`Argument ${key} item requires non-empty contentJson`);
|
|
1888
|
+
}
|
|
1889
|
+
const nodes = contentJson.flatMap((node) => {
|
|
1890
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
1891
|
+
return [];
|
|
1892
|
+
}
|
|
1893
|
+
return [node];
|
|
1894
|
+
});
|
|
1895
|
+
if (nodes.length === 0) {
|
|
1896
|
+
throw new Error(`Argument ${key} item requires non-empty contentJson`);
|
|
1897
|
+
}
|
|
1898
|
+
return {
|
|
1899
|
+
name,
|
|
1900
|
+
contentJson: nodes
|
|
1901
|
+
};
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
function resolveApiBaseUrl(options) {
|
|
1905
|
+
return (options.apiBaseUrl ?? process.env.MAILTEA_API_BASE_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
1906
|
+
}
|
|
1907
|
+
function resolveToken(options) {
|
|
1908
|
+
return options.token ?? process.env.MAILTEA_API_TOKEN ?? null;
|
|
1909
|
+
}
|
|
1910
|
+
function resolvePublicationId(options) {
|
|
1911
|
+
return asOptionalString(options.publicationId ?? void 0) ?? asOptionalString(process.env.MAILTEA_PUBLICATION_ID) ?? null;
|
|
1912
|
+
}
|
|
1913
|
+
function parseAnalyticsSummaryUri(uri) {
|
|
1914
|
+
let parsed;
|
|
1915
|
+
try {
|
|
1916
|
+
parsed = new URL(uri);
|
|
1917
|
+
} catch {
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
if (parsed.protocol !== "analytics:" || parsed.hostname !== "current" || parsed.pathname !== "/latest-summary") {
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
const publicationId = asOptionalString(parsed.searchParams.get("publicationId"));
|
|
1924
|
+
const range = parseIssueAnalyticsRange(asOptionalString(parsed.searchParams.get("range")), "range") ?? "7d";
|
|
1925
|
+
return { publicationId, range };
|
|
1926
|
+
}
|
|
1927
|
+
async function loadLatestAnalyticsSummary(options, input) {
|
|
1928
|
+
const publicationId = input.publicationId ?? resolvePublicationId(options);
|
|
1929
|
+
if (!publicationId) {
|
|
1930
|
+
return {
|
|
1931
|
+
status: "missing_publication_context",
|
|
1932
|
+
message: "Set MAILTEA_PUBLICATION_ID or pass publicationId in the request.",
|
|
1933
|
+
exampleUri: "analytics://current/latest-summary?publicationId=pub_demo"
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
const summary = await callTrpc(
|
|
1937
|
+
"issue.latestSummary",
|
|
1938
|
+
{
|
|
1939
|
+
publicationId,
|
|
1940
|
+
range: input.range
|
|
1941
|
+
},
|
|
1942
|
+
options,
|
|
1943
|
+
"query"
|
|
1944
|
+
);
|
|
1945
|
+
return summary;
|
|
1946
|
+
}
|
|
1947
|
+
function makeToolResult(text, structuredContent) {
|
|
1948
|
+
return {
|
|
1949
|
+
content: [
|
|
1950
|
+
{
|
|
1951
|
+
type: "text",
|
|
1952
|
+
text
|
|
1953
|
+
}
|
|
1954
|
+
],
|
|
1955
|
+
...structuredContent ? { structuredContent } : {}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function toErrorMessage(err) {
|
|
1959
|
+
return err instanceof Error ? err.message : "Internal error";
|
|
1960
|
+
}
|
|
1961
|
+
async function callTrpc(path, input, options, procedureType = "mutation") {
|
|
1962
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1963
|
+
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
1964
|
+
const token = resolveToken(options);
|
|
1965
|
+
if (!token) {
|
|
1966
|
+
throw new Error(
|
|
1967
|
+
"Missing API token. Set MAILTEA_API_TOKEN or pass Authorization header when using /mcp."
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
const headers = {
|
|
1971
|
+
authorization: `Bearer ${token}`
|
|
1972
|
+
};
|
|
1973
|
+
const request = procedureType === "query" ? {
|
|
1974
|
+
url: `${apiBaseUrl}/trpc/${path}?input=${encodeURIComponent(
|
|
1975
|
+
JSON.stringify(input ?? {})
|
|
1976
|
+
)}`,
|
|
1977
|
+
init: {
|
|
1978
|
+
method: "GET",
|
|
1979
|
+
headers
|
|
1980
|
+
}
|
|
1981
|
+
} : {
|
|
1982
|
+
url: `${apiBaseUrl}/trpc/${path}`,
|
|
1983
|
+
init: {
|
|
1984
|
+
method: "POST",
|
|
1985
|
+
headers: {
|
|
1986
|
+
...headers,
|
|
1987
|
+
"content-type": "application/json"
|
|
1988
|
+
},
|
|
1989
|
+
body: JSON.stringify(input)
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
const httpResponse = await fetchImpl(request.url, request.init);
|
|
1993
|
+
const payload = await httpResponse.json().catch(() => null);
|
|
1994
|
+
if (!httpResponse.ok || payload?.error) {
|
|
1995
|
+
throw new Error(
|
|
1996
|
+
payload?.error?.message ?? `${httpResponse.status} ${httpResponse.statusText}`
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
if (!payload?.result || typeof payload.result !== "object" || !("data" in payload.result)) {
|
|
2000
|
+
throw new Error(`Malformed tRPC response for ${path}`);
|
|
2001
|
+
}
|
|
2002
|
+
return payload.result.data;
|
|
2003
|
+
}
|
|
2004
|
+
async function callRestApi(method, path, body, options) {
|
|
2005
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2006
|
+
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
2007
|
+
const token = resolveToken(options);
|
|
2008
|
+
if (!token) {
|
|
2009
|
+
throw new Error("Missing API token.");
|
|
2010
|
+
}
|
|
2011
|
+
const headers = {
|
|
2012
|
+
authorization: `Bearer ${token}`
|
|
2013
|
+
};
|
|
2014
|
+
const init = { method, headers };
|
|
2015
|
+
if (body !== void 0) {
|
|
2016
|
+
headers["content-type"] = "application/json";
|
|
2017
|
+
init.body = JSON.stringify(body);
|
|
2018
|
+
}
|
|
2019
|
+
const httpResponse = await fetchImpl(`${apiBaseUrl}${path}`, init);
|
|
2020
|
+
const payload = await httpResponse.json().catch(() => null);
|
|
2021
|
+
if (!httpResponse.ok) {
|
|
2022
|
+
throw new Error(
|
|
2023
|
+
payload?.error ?? `${httpResponse.status} ${httpResponse.statusText}`
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
return payload;
|
|
2027
|
+
}
|
|
2028
|
+
var EMAIL_SEND_FIELDS = [
|
|
2029
|
+
"from",
|
|
2030
|
+
"to",
|
|
2031
|
+
"subject",
|
|
2032
|
+
"html",
|
|
2033
|
+
"text",
|
|
2034
|
+
"template",
|
|
2035
|
+
"cc",
|
|
2036
|
+
"bcc",
|
|
2037
|
+
"reply_to",
|
|
2038
|
+
"scheduled_at",
|
|
2039
|
+
"tags",
|
|
2040
|
+
"headers",
|
|
2041
|
+
"attachments"
|
|
2042
|
+
];
|
|
2043
|
+
async function runTool(toolName, args, options) {
|
|
2044
|
+
if (toolName === "auth.me") {
|
|
2045
|
+
const me = await callTrpc("auth.me", {}, options, "query");
|
|
2046
|
+
return makeToolResult(
|
|
2047
|
+
me.userId ? `Authenticated as ${me.role} (${me.userId})` : "Not authenticated",
|
|
2048
|
+
me
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
if (toolName === "issue.create_draft") {
|
|
2052
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2053
|
+
const title = readRequiredString(args, "title");
|
|
2054
|
+
const kind = args.kind === "broadcast" ? "broadcast" : args.kind === "newsletter" ? "newsletter" : void 0;
|
|
2055
|
+
const templateId = asOptionalString(args.templateId);
|
|
2056
|
+
const variables = args.variables;
|
|
2057
|
+
const contentHtml = asOptionalString(args.contentHtml);
|
|
2058
|
+
const contentSpec = args.contentSpec;
|
|
2059
|
+
let resolvedHtml = contentHtml;
|
|
2060
|
+
if (!templateId && contentSpec && typeof contentSpec === "object" && contentSpec.root) {
|
|
2061
|
+
const rendered = await callRestApi(
|
|
2062
|
+
"POST",
|
|
2063
|
+
"/v1/templates/render",
|
|
2064
|
+
{ spec: contentSpec },
|
|
2065
|
+
options
|
|
2066
|
+
);
|
|
2067
|
+
resolvedHtml = rendered.html;
|
|
2068
|
+
}
|
|
2069
|
+
const draft = await callTrpc(
|
|
2070
|
+
"issue.createDraft",
|
|
2071
|
+
{
|
|
2072
|
+
publicationId,
|
|
2073
|
+
title,
|
|
2074
|
+
...kind ? { kind } : {},
|
|
2075
|
+
...templateId ? { templateId, ...variables ? { variables } : {} } : resolvedHtml ? {
|
|
2076
|
+
contentJson: {
|
|
2077
|
+
type: "mailtea.rich.v1",
|
|
2078
|
+
html: resolvedHtml
|
|
2079
|
+
}
|
|
2080
|
+
} : {}
|
|
2081
|
+
},
|
|
2082
|
+
options
|
|
2083
|
+
);
|
|
2084
|
+
return makeToolResult(`Draft created: ${draft.id}`, { issue: draft });
|
|
2085
|
+
}
|
|
2086
|
+
if (toolName === "issue.get_editor") {
|
|
2087
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2088
|
+
const issue = await callTrpc(
|
|
2089
|
+
"issue.getEditor",
|
|
2090
|
+
{ issueId },
|
|
2091
|
+
options,
|
|
2092
|
+
"query"
|
|
2093
|
+
);
|
|
2094
|
+
return makeToolResult(`Loaded editor state for ${issue.id} (${issue.status})`, { issue });
|
|
2095
|
+
}
|
|
2096
|
+
if (toolName === "issue.update_draft") {
|
|
2097
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2098
|
+
const title = readRequiredString(args, "title");
|
|
2099
|
+
const contentHtml = asOptionalString(args.contentHtml);
|
|
2100
|
+
const contentSpec = args.contentSpec;
|
|
2101
|
+
let resolvedHtml = contentHtml;
|
|
2102
|
+
if (contentSpec && typeof contentSpec === "object" && contentSpec.root) {
|
|
2103
|
+
const rendered = await callRestApi(
|
|
2104
|
+
"POST",
|
|
2105
|
+
"/v1/templates/render",
|
|
2106
|
+
{ spec: contentSpec },
|
|
2107
|
+
options
|
|
2108
|
+
);
|
|
2109
|
+
resolvedHtml = rendered.html;
|
|
2110
|
+
}
|
|
2111
|
+
const draft = await callTrpc(
|
|
2112
|
+
"issue.updateDraft",
|
|
2113
|
+
{
|
|
2114
|
+
issueId,
|
|
2115
|
+
title,
|
|
2116
|
+
...resolvedHtml ? {
|
|
2117
|
+
contentJson: {
|
|
2118
|
+
type: "mailtea.rich.v1",
|
|
2119
|
+
html: resolvedHtml
|
|
2120
|
+
}
|
|
2121
|
+
} : {}
|
|
2122
|
+
},
|
|
2123
|
+
options
|
|
2124
|
+
);
|
|
2125
|
+
return makeToolResult(`Draft updated: ${draft.id}`, { issue: draft });
|
|
2126
|
+
}
|
|
2127
|
+
if (toolName === "issue.remove_draft") {
|
|
2128
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2129
|
+
const removed = await callTrpc(
|
|
2130
|
+
"issue.removeDraft",
|
|
2131
|
+
{ issueId },
|
|
2132
|
+
options
|
|
2133
|
+
);
|
|
2134
|
+
return makeToolResult(`Draft removed: ${removed.issueId}`, removed);
|
|
2135
|
+
}
|
|
2136
|
+
if (toolName === "issue.list_recent") {
|
|
2137
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2138
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2139
|
+
const limit = Math.max(1, Math.min(50, Math.trunc(requestedLimit ?? 10)));
|
|
2140
|
+
const rows = await callTrpc(
|
|
2141
|
+
"issue.listRecent",
|
|
2142
|
+
{ publicationId, limit },
|
|
2143
|
+
options,
|
|
2144
|
+
"query"
|
|
2145
|
+
);
|
|
2146
|
+
return makeToolResult(
|
|
2147
|
+
rows.length === 0 ? `No issues found for ${publicationId}` : `Loaded ${rows.length} issues for ${publicationId}`,
|
|
2148
|
+
{
|
|
2149
|
+
publicationId,
|
|
2150
|
+
issues: rows
|
|
2151
|
+
}
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
if (toolName === "publication.list") {
|
|
2155
|
+
const workspaces = await callTrpc(
|
|
2156
|
+
"publication.listMine",
|
|
2157
|
+
{},
|
|
2158
|
+
options,
|
|
2159
|
+
"query"
|
|
2160
|
+
);
|
|
2161
|
+
return makeToolResult(
|
|
2162
|
+
workspaces.length === 0 ? "No publication workspaces available for this identity yet" : `Loaded ${workspaces.length} publication workspaces`,
|
|
2163
|
+
{
|
|
2164
|
+
publications: workspaces
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
if (toolName === "publication.create") {
|
|
2169
|
+
const publicationId = asOptionalString(args.publicationId);
|
|
2170
|
+
const name = readRequiredString(args, "name");
|
|
2171
|
+
const timezone = asOptionalString(args.timezone);
|
|
2172
|
+
const result = await callTrpc(
|
|
2173
|
+
"publication.create",
|
|
2174
|
+
{
|
|
2175
|
+
...publicationId ? { publicationId } : {},
|
|
2176
|
+
name,
|
|
2177
|
+
...timezone ? { timezone } : {}
|
|
2178
|
+
},
|
|
2179
|
+
options
|
|
2180
|
+
);
|
|
2181
|
+
return makeToolResult(`Publication created: ${result.publication.id}`, result);
|
|
2182
|
+
}
|
|
2183
|
+
if (toolName === "publication.domain_list") {
|
|
2184
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2185
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2186
|
+
const limit = Math.max(1, Math.min(100, Math.trunc(requestedLimit ?? 50)));
|
|
2187
|
+
const domains = await callTrpc(
|
|
2188
|
+
"publication.domains",
|
|
2189
|
+
{
|
|
2190
|
+
publicationId,
|
|
2191
|
+
limit
|
|
2192
|
+
},
|
|
2193
|
+
options,
|
|
2194
|
+
"query"
|
|
2195
|
+
);
|
|
2196
|
+
return makeToolResult(
|
|
2197
|
+
domains.length === 0 ? `No custom domains configured for ${publicationId}` : `Loaded ${domains.length} custom domains for ${publicationId}`,
|
|
2198
|
+
{ publicationId, domains }
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
if (toolName === "publication.domain_upsert") {
|
|
2202
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2203
|
+
const host = readRequiredString(args, "host");
|
|
2204
|
+
const isPrimary = readOptionalBoolean(args, "isPrimary");
|
|
2205
|
+
const proxyTarget = asOptionalString(args.proxyTarget);
|
|
2206
|
+
const result = await callTrpc(
|
|
2207
|
+
"publication.domainUpsert",
|
|
2208
|
+
{
|
|
2209
|
+
publicationId,
|
|
2210
|
+
host,
|
|
2211
|
+
...typeof isPrimary === "boolean" ? { isPrimary } : {},
|
|
2212
|
+
...proxyTarget ? { proxyTarget } : {}
|
|
2213
|
+
},
|
|
2214
|
+
options
|
|
2215
|
+
);
|
|
2216
|
+
return makeToolResult(
|
|
2217
|
+
`Publication domain saved: ${result.domain.host} (${result.domain.status})`,
|
|
2218
|
+
result
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
if (toolName === "publication.domain_verify") {
|
|
2222
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2223
|
+
const domainId = readRequiredString(args, "domainId");
|
|
2224
|
+
const verificationValue = asOptionalString(args.verificationValue);
|
|
2225
|
+
const result = await callTrpc(
|
|
2226
|
+
"publication.domainVerify",
|
|
2227
|
+
{
|
|
2228
|
+
publicationId,
|
|
2229
|
+
domainId,
|
|
2230
|
+
...verificationValue ? { verificationValue } : {}
|
|
2231
|
+
},
|
|
2232
|
+
options
|
|
2233
|
+
);
|
|
2234
|
+
return makeToolResult(
|
|
2235
|
+
`Publication domain verified: ${result.domain.host}`,
|
|
2236
|
+
result
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
if (toolName === "publication.domain_set_primary") {
|
|
2240
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2241
|
+
const domainId = readRequiredString(args, "domainId");
|
|
2242
|
+
const result = await callTrpc(
|
|
2243
|
+
"publication.domainSetPrimary",
|
|
2244
|
+
{
|
|
2245
|
+
publicationId,
|
|
2246
|
+
domainId
|
|
2247
|
+
},
|
|
2248
|
+
options
|
|
2249
|
+
);
|
|
2250
|
+
return makeToolResult(`Primary domain set: ${result.domain.host}`, result);
|
|
2251
|
+
}
|
|
2252
|
+
if (toolName === "publication.domain_remove") {
|
|
2253
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2254
|
+
const domainId = readRequiredString(args, "domainId");
|
|
2255
|
+
const result = await callTrpc(
|
|
2256
|
+
"publication.domainRemove",
|
|
2257
|
+
{
|
|
2258
|
+
publicationId,
|
|
2259
|
+
domainId
|
|
2260
|
+
},
|
|
2261
|
+
options
|
|
2262
|
+
);
|
|
2263
|
+
return makeToolResult(`Publication domain removed: ${result.domainId}`, result);
|
|
2264
|
+
}
|
|
2265
|
+
if (toolName === "publication.domain_traefik_preview") {
|
|
2266
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2267
|
+
const preview = await callTrpc(
|
|
2268
|
+
"publication.domainTraefikPreview",
|
|
2269
|
+
{
|
|
2270
|
+
publicationId
|
|
2271
|
+
},
|
|
2272
|
+
options,
|
|
2273
|
+
"query"
|
|
2274
|
+
);
|
|
2275
|
+
return makeToolResult(
|
|
2276
|
+
`Generated Traefik preview for ${publicationId} (${preview.entries.length} domains)`,
|
|
2277
|
+
preview
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
if (toolName === "contact.list") {
|
|
2281
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2282
|
+
const status = readContactStatus(args, "status");
|
|
2283
|
+
const query = asOptionalString(args.query);
|
|
2284
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2285
|
+
const limit = Math.max(1, Math.min(500, Math.trunc(requestedLimit ?? 200)));
|
|
2286
|
+
const contacts = await callTrpc(
|
|
2287
|
+
"contact.list",
|
|
2288
|
+
{
|
|
2289
|
+
publicationId,
|
|
2290
|
+
...status ? { status } : {},
|
|
2291
|
+
...query ? { query } : {},
|
|
2292
|
+
limit
|
|
2293
|
+
},
|
|
2294
|
+
options,
|
|
2295
|
+
"query"
|
|
2296
|
+
);
|
|
2297
|
+
return makeToolResult(
|
|
2298
|
+
contacts.length === 0 ? `No contacts found for ${publicationId}` : `Loaded ${contacts.length} contacts for ${publicationId}`,
|
|
2299
|
+
{
|
|
2300
|
+
publicationId,
|
|
2301
|
+
contacts
|
|
2302
|
+
}
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
if (toolName === "contact.upsert") {
|
|
2306
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2307
|
+
const email = readRequiredString(args, "email");
|
|
2308
|
+
const referrerContactId = asOptionalString(args.referrerContactId);
|
|
2309
|
+
const contact = await callTrpc(
|
|
2310
|
+
"contact.upsert",
|
|
2311
|
+
{
|
|
2312
|
+
publicationId,
|
|
2313
|
+
email,
|
|
2314
|
+
...referrerContactId ? { referrerContactId } : {}
|
|
2315
|
+
},
|
|
2316
|
+
options
|
|
2317
|
+
);
|
|
2318
|
+
return makeToolResult(`Contact saved: ${contact.id} (${contact.status})`, {
|
|
2319
|
+
contact
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
if (toolName === "contact.set_status") {
|
|
2323
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2324
|
+
const contactId = readRequiredString(args, "contactId");
|
|
2325
|
+
const status = readContactStatus(args, "status");
|
|
2326
|
+
if (!status) {
|
|
2327
|
+
throw new Error("status is required");
|
|
2328
|
+
}
|
|
2329
|
+
const contact = await callTrpc(
|
|
2330
|
+
"contact.setStatus",
|
|
2331
|
+
{
|
|
2332
|
+
publicationId,
|
|
2333
|
+
contactId,
|
|
2334
|
+
status
|
|
2335
|
+
},
|
|
2336
|
+
options
|
|
2337
|
+
);
|
|
2338
|
+
return makeToolResult(`Contact status updated: ${contact.id} -> ${contact.status}`, {
|
|
2339
|
+
contact
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
if (toolName === "contact.import_csv") {
|
|
2343
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2344
|
+
const csvText = readRequiredString(args, "csvText");
|
|
2345
|
+
const result = await callTrpc(
|
|
2346
|
+
"contact.importCsv",
|
|
2347
|
+
{
|
|
2348
|
+
publicationId,
|
|
2349
|
+
csvText
|
|
2350
|
+
},
|
|
2351
|
+
options
|
|
2352
|
+
);
|
|
2353
|
+
return makeToolResult(
|
|
2354
|
+
`Contact import complete for ${publicationId}: +${result.createdCount} new, ${result.reactivatedCount} reactivated, ${result.invalidCount} invalid`,
|
|
2355
|
+
result
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
if (toolName === "contact.referral_summary") {
|
|
2359
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2360
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2361
|
+
const limit = Math.max(1, Math.min(100, Math.trunc(requestedLimit ?? 20)));
|
|
2362
|
+
const summary = await callTrpc(
|
|
2363
|
+
"contact.referralSummary",
|
|
2364
|
+
{
|
|
2365
|
+
publicationId,
|
|
2366
|
+
limit
|
|
2367
|
+
},
|
|
2368
|
+
options,
|
|
2369
|
+
"query"
|
|
2370
|
+
);
|
|
2371
|
+
return makeToolResult(
|
|
2372
|
+
summary.totalReferrals === 0 ? `No referrals tracked yet for ${publicationId}` : `Loaded referral summary for ${publicationId}: ${summary.totalReferrals} total referrals`,
|
|
2373
|
+
summary
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
if (toolName === "contact.referral_milestones") {
|
|
2377
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2378
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2379
|
+
const limit = Math.max(1, Math.min(200, Math.trunc(requestedLimit ?? 100)));
|
|
2380
|
+
const milestones = await callTrpc(
|
|
2381
|
+
"contact.referralMilestones",
|
|
2382
|
+
{
|
|
2383
|
+
publicationId,
|
|
2384
|
+
limit
|
|
2385
|
+
},
|
|
2386
|
+
options,
|
|
2387
|
+
"query"
|
|
2388
|
+
);
|
|
2389
|
+
return makeToolResult(
|
|
2390
|
+
milestones.length === 0 ? `No referral milestones configured for ${publicationId}` : `Loaded ${milestones.length} referral milestones for ${publicationId}`,
|
|
2391
|
+
{ publicationId, milestones }
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
if (toolName === "contact.referral_milestone_upsert") {
|
|
2395
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2396
|
+
const title = readRequiredString(args, "title");
|
|
2397
|
+
const description = asOptionalString(args.description);
|
|
2398
|
+
const milestoneId = asOptionalString(args.milestoneId);
|
|
2399
|
+
const referralCountInput = readOptionalNumber(args, "referralCount");
|
|
2400
|
+
if (typeof referralCountInput !== "number" || !Number.isFinite(referralCountInput)) {
|
|
2401
|
+
throw new Error("referralCount is required and must be a number");
|
|
2402
|
+
}
|
|
2403
|
+
const referralCount = Math.max(1, Math.trunc(referralCountInput));
|
|
2404
|
+
const result = await callTrpc(
|
|
2405
|
+
"contact.upsertReferralMilestone",
|
|
2406
|
+
{
|
|
2407
|
+
publicationId,
|
|
2408
|
+
...milestoneId ? { milestoneId } : {},
|
|
2409
|
+
title,
|
|
2410
|
+
...description ? { description } : {},
|
|
2411
|
+
referralCount
|
|
2412
|
+
},
|
|
2413
|
+
options
|
|
2414
|
+
);
|
|
2415
|
+
return makeToolResult(
|
|
2416
|
+
`Referral milestone saved: ${result.milestone.id} (${result.rewardedCount} rewards granted)`,
|
|
2417
|
+
result
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
if (toolName === "contact.referral_milestone_remove") {
|
|
2421
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2422
|
+
const milestoneId = readRequiredString(args, "milestoneId");
|
|
2423
|
+
const result = await callTrpc(
|
|
2424
|
+
"contact.removeReferralMilestone",
|
|
2425
|
+
{
|
|
2426
|
+
publicationId,
|
|
2427
|
+
milestoneId
|
|
2428
|
+
},
|
|
2429
|
+
options
|
|
2430
|
+
);
|
|
2431
|
+
return makeToolResult(`Referral milestone removed: ${result.milestoneId}`, result);
|
|
2432
|
+
}
|
|
2433
|
+
if (toolName === "contact.referral_rewards") {
|
|
2434
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2435
|
+
const contactId = asOptionalString(args.contactId);
|
|
2436
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2437
|
+
const limit = Math.max(1, Math.min(500, Math.trunc(requestedLimit ?? 100)));
|
|
2438
|
+
const rewards = await callTrpc(
|
|
2439
|
+
"contact.referralRewards",
|
|
2440
|
+
{
|
|
2441
|
+
publicationId,
|
|
2442
|
+
...contactId ? { contactId } : {},
|
|
2443
|
+
limit
|
|
2444
|
+
},
|
|
2445
|
+
options,
|
|
2446
|
+
"query"
|
|
2447
|
+
);
|
|
2448
|
+
return makeToolResult(
|
|
2449
|
+
rewards.length === 0 ? `No referral rewards found for ${publicationId}` : `Loaded ${rewards.length} referral rewards for ${publicationId}`,
|
|
2450
|
+
{ publicationId, rewards }
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
if (toolName === "monetize.offer_list") {
|
|
2454
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2455
|
+
const status = readSponsorOfferStatus(args, "status");
|
|
2456
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2457
|
+
const limit = Math.max(1, Math.min(200, Math.trunc(requestedLimit ?? 100)));
|
|
2458
|
+
const offers = await callTrpc(
|
|
2459
|
+
"monetize.listOffers",
|
|
2460
|
+
{
|
|
2461
|
+
publicationId,
|
|
2462
|
+
...status ? { status } : {},
|
|
2463
|
+
limit
|
|
2464
|
+
},
|
|
2465
|
+
options,
|
|
2466
|
+
"query"
|
|
2467
|
+
);
|
|
2468
|
+
return makeToolResult(
|
|
2469
|
+
offers.length === 0 ? `No sponsor offers found for ${publicationId}` : `Loaded ${offers.length} sponsor offers for ${publicationId}`,
|
|
2470
|
+
{ publicationId, offers }
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
if (toolName === "monetize.offer_upsert") {
|
|
2474
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2475
|
+
const offerId = asOptionalString(args.offerId);
|
|
2476
|
+
const title = readRequiredString(args, "title");
|
|
2477
|
+
const sponsorName = readRequiredString(args, "sponsorName");
|
|
2478
|
+
const description = asOptionalString(args.description);
|
|
2479
|
+
const pricingModel = readSponsorOfferPricingModel(args, "pricingModel");
|
|
2480
|
+
if (!pricingModel) {
|
|
2481
|
+
throw new Error("pricingModel is required");
|
|
2482
|
+
}
|
|
2483
|
+
const rateCentsInput = readOptionalNumber(args, "rateCents");
|
|
2484
|
+
if (typeof rateCentsInput !== "number" || !Number.isFinite(rateCentsInput)) {
|
|
2485
|
+
throw new Error("rateCents is required and must be a number");
|
|
2486
|
+
}
|
|
2487
|
+
const rateCents = Math.max(1, Math.trunc(rateCentsInput));
|
|
2488
|
+
const estimatedPlacementsInput = readOptionalNumber(args, "estimatedPlacements");
|
|
2489
|
+
const estimatedPlacements = typeof estimatedPlacementsInput === "number" && Number.isFinite(estimatedPlacementsInput) ? Math.max(1, Math.trunc(estimatedPlacementsInput)) : void 0;
|
|
2490
|
+
const status = readSponsorOfferStatus(args, "status");
|
|
2491
|
+
const startsAt = asOptionalString(args.startsAt);
|
|
2492
|
+
const endsAt = asOptionalString(args.endsAt);
|
|
2493
|
+
const result = await callTrpc(
|
|
2494
|
+
"monetize.upsertOffer",
|
|
2495
|
+
{
|
|
2496
|
+
publicationId,
|
|
2497
|
+
...offerId ? { offerId } : {},
|
|
2498
|
+
title,
|
|
2499
|
+
sponsorName,
|
|
2500
|
+
...description ? { description } : {},
|
|
2501
|
+
pricingModel,
|
|
2502
|
+
rateCents,
|
|
2503
|
+
...typeof estimatedPlacements === "number" ? { estimatedPlacements } : {},
|
|
2504
|
+
...status ? { status } : {},
|
|
2505
|
+
...startsAt ? { startsAt } : {},
|
|
2506
|
+
...endsAt ? { endsAt } : {}
|
|
2507
|
+
},
|
|
2508
|
+
options
|
|
2509
|
+
);
|
|
2510
|
+
return makeToolResult(`Sponsor offer saved: ${result.offer.id}`, result);
|
|
2511
|
+
}
|
|
2512
|
+
if (toolName === "monetize.offer_remove") {
|
|
2513
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2514
|
+
const offerId = readRequiredString(args, "offerId");
|
|
2515
|
+
const result = await callTrpc(
|
|
2516
|
+
"monetize.removeOffer",
|
|
2517
|
+
{
|
|
2518
|
+
publicationId,
|
|
2519
|
+
offerId
|
|
2520
|
+
},
|
|
2521
|
+
options
|
|
2522
|
+
);
|
|
2523
|
+
return makeToolResult(`Sponsor offer removed: ${result.offerId}`, result);
|
|
2524
|
+
}
|
|
2525
|
+
if (toolName === "section.list") {
|
|
2526
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2527
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2528
|
+
const limit = Math.max(1, Math.min(200, Math.trunc(requestedLimit ?? 100)));
|
|
2529
|
+
const rows = await callTrpc(
|
|
2530
|
+
"section.list",
|
|
2531
|
+
{ publicationId, limit },
|
|
2532
|
+
options,
|
|
2533
|
+
"query"
|
|
2534
|
+
);
|
|
2535
|
+
return makeToolResult(
|
|
2536
|
+
rows.length === 0 ? `No reusable sections found for ${publicationId}` : `Loaded ${rows.length} reusable sections for ${publicationId}`,
|
|
2537
|
+
{
|
|
2538
|
+
publicationId,
|
|
2539
|
+
sections: rows
|
|
2540
|
+
}
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
if (toolName === "section.catalog") {
|
|
2544
|
+
const publicationId = asOptionalString(args.publicationId);
|
|
2545
|
+
const packs = await callTrpc(
|
|
2546
|
+
"section.catalog",
|
|
2547
|
+
publicationId ? { publicationId } : {},
|
|
2548
|
+
options,
|
|
2549
|
+
"query"
|
|
2550
|
+
);
|
|
2551
|
+
return makeToolResult(
|
|
2552
|
+
packs.length === 0 ? "No marketplace packs available" : `Loaded ${packs.length} marketplace packs`,
|
|
2553
|
+
{ packs }
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
if (toolName === "section.pack_create") {
|
|
2557
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2558
|
+
const title = readRequiredString(args, "title");
|
|
2559
|
+
const description = asOptionalString(args.description);
|
|
2560
|
+
const styleProfile = readOptionalJsonObject(args, "styleProfile");
|
|
2561
|
+
const sections = readRequiredPackSections(args, "sections");
|
|
2562
|
+
const result = await callTrpc(
|
|
2563
|
+
"section.createPack",
|
|
2564
|
+
{
|
|
2565
|
+
publicationId,
|
|
2566
|
+
title,
|
|
2567
|
+
...description ? { description } : {},
|
|
2568
|
+
...styleProfile ? { styleProfile } : {},
|
|
2569
|
+
sections
|
|
2570
|
+
},
|
|
2571
|
+
options
|
|
2572
|
+
);
|
|
2573
|
+
return makeToolResult(`Marketplace pack created: ${result.pack.id}`, result);
|
|
2574
|
+
}
|
|
2575
|
+
if (toolName === "section.pack_update") {
|
|
2576
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2577
|
+
const packId = readRequiredString(args, "packId");
|
|
2578
|
+
const title = readRequiredString(args, "title");
|
|
2579
|
+
const description = asOptionalString(args.description);
|
|
2580
|
+
const styleProfile = readOptionalJsonObject(args, "styleProfile");
|
|
2581
|
+
const sections = readRequiredPackSections(args, "sections");
|
|
2582
|
+
const result = await callTrpc(
|
|
2583
|
+
"section.updatePack",
|
|
2584
|
+
{
|
|
2585
|
+
publicationId,
|
|
2586
|
+
packId,
|
|
2587
|
+
title,
|
|
2588
|
+
...description ? { description } : {},
|
|
2589
|
+
...styleProfile ? { styleProfile } : {},
|
|
2590
|
+
sections
|
|
2591
|
+
},
|
|
2592
|
+
options
|
|
2593
|
+
);
|
|
2594
|
+
return makeToolResult(`Marketplace pack updated: ${result.pack.id}`, result);
|
|
2595
|
+
}
|
|
2596
|
+
if (toolName === "section.pack_remove") {
|
|
2597
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2598
|
+
const packId = readRequiredString(args, "packId");
|
|
2599
|
+
const result = await callTrpc(
|
|
2600
|
+
"section.removePack",
|
|
2601
|
+
{
|
|
2602
|
+
publicationId,
|
|
2603
|
+
packId
|
|
2604
|
+
},
|
|
2605
|
+
options
|
|
2606
|
+
);
|
|
2607
|
+
return makeToolResult(`Marketplace pack removed: ${result.packId}`, result);
|
|
2608
|
+
}
|
|
2609
|
+
if (toolName === "section.pack_revisions") {
|
|
2610
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2611
|
+
const packId = readRequiredString(args, "packId");
|
|
2612
|
+
const requestedLimit = readOptionalNumber(args, "limit");
|
|
2613
|
+
const limit = Math.max(1, Math.min(100, Math.trunc(requestedLimit ?? 30)));
|
|
2614
|
+
const result = await callTrpc(
|
|
2615
|
+
"section.revisions",
|
|
2616
|
+
{
|
|
2617
|
+
publicationId,
|
|
2618
|
+
packId,
|
|
2619
|
+
limit
|
|
2620
|
+
},
|
|
2621
|
+
options,
|
|
2622
|
+
"query"
|
|
2623
|
+
);
|
|
2624
|
+
return makeToolResult(
|
|
2625
|
+
result.length === 0 ? `No revisions found for ${packId}` : `Loaded ${result.length} revisions for ${packId}`,
|
|
2626
|
+
{
|
|
2627
|
+
publicationId,
|
|
2628
|
+
packId,
|
|
2629
|
+
revisions: result
|
|
2630
|
+
}
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
if (toolName === "section.pack_restore_revision") {
|
|
2634
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2635
|
+
const packId = readRequiredString(args, "packId");
|
|
2636
|
+
const revisionId = readRequiredString(args, "revisionId");
|
|
2637
|
+
const result = await callTrpc(
|
|
2638
|
+
"section.restorePackRevision",
|
|
2639
|
+
{
|
|
2640
|
+
publicationId,
|
|
2641
|
+
packId,
|
|
2642
|
+
revisionId
|
|
2643
|
+
},
|
|
2644
|
+
options
|
|
2645
|
+
);
|
|
2646
|
+
return makeToolResult(
|
|
2647
|
+
`Marketplace pack restored: ${result.pack.id} (from v${result.restoredFrom.version} -> v${result.createdRevision.version})`,
|
|
2648
|
+
result
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2651
|
+
if (toolName === "section.import_pack") {
|
|
2652
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2653
|
+
const presetId = readRequiredString(args, "presetId");
|
|
2654
|
+
const result = await callTrpc(
|
|
2655
|
+
"section.importPack",
|
|
2656
|
+
{
|
|
2657
|
+
publicationId,
|
|
2658
|
+
presetId
|
|
2659
|
+
},
|
|
2660
|
+
options
|
|
2661
|
+
);
|
|
2662
|
+
return makeToolResult(
|
|
2663
|
+
`Imported pack ${result.presetId}: ${result.createdCount} created, ${result.updatedCount} updated`,
|
|
2664
|
+
result
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
if (toolName === "section.create") {
|
|
2668
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2669
|
+
const name = readRequiredString(args, "name");
|
|
2670
|
+
const contentJson = readRequiredJsonObjectArray(args, "contentJson");
|
|
2671
|
+
const section = await callTrpc(
|
|
2672
|
+
"section.create",
|
|
2673
|
+
{
|
|
2674
|
+
publicationId,
|
|
2675
|
+
name,
|
|
2676
|
+
contentJson
|
|
2677
|
+
},
|
|
2678
|
+
options
|
|
2679
|
+
);
|
|
2680
|
+
return makeToolResult(`Reusable section saved: ${section.id}`, { section });
|
|
2681
|
+
}
|
|
2682
|
+
if (toolName === "section.update") {
|
|
2683
|
+
const sectionId = readRequiredString(args, "sectionId");
|
|
2684
|
+
const name = readRequiredString(args, "name");
|
|
2685
|
+
const contentJson = readRequiredJsonObjectArray(args, "contentJson");
|
|
2686
|
+
const section = await callTrpc(
|
|
2687
|
+
"section.update",
|
|
2688
|
+
{
|
|
2689
|
+
sectionId,
|
|
2690
|
+
name,
|
|
2691
|
+
contentJson
|
|
2692
|
+
},
|
|
2693
|
+
options
|
|
2694
|
+
);
|
|
2695
|
+
return makeToolResult(`Reusable section updated: ${section.id}`, { section });
|
|
2696
|
+
}
|
|
2697
|
+
if (toolName === "section.remove") {
|
|
2698
|
+
const sectionId = readRequiredString(args, "sectionId");
|
|
2699
|
+
const result = await callTrpc(
|
|
2700
|
+
"section.remove",
|
|
2701
|
+
{ sectionId },
|
|
2702
|
+
options
|
|
2703
|
+
);
|
|
2704
|
+
return makeToolResult(`Reusable section removed: ${result.sectionId}`, result);
|
|
2705
|
+
}
|
|
2706
|
+
if (toolName === "issue.preview") {
|
|
2707
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2708
|
+
const preview = await callTrpc(
|
|
2709
|
+
"issue.preview",
|
|
2710
|
+
{ issueId },
|
|
2711
|
+
options,
|
|
2712
|
+
"query"
|
|
2713
|
+
);
|
|
2714
|
+
return makeToolResult(`Preview generated for ${preview.issueId}`, preview);
|
|
2715
|
+
}
|
|
2716
|
+
if (toolName === "issue.preview_draft") {
|
|
2717
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2718
|
+
const title = readRequiredString(args, "title");
|
|
2719
|
+
const html = readRequiredString(args, "html");
|
|
2720
|
+
const plainText = asOptionalString(args.plainText);
|
|
2721
|
+
const styleProfile = readOptionalJsonObject(args, "styleProfile");
|
|
2722
|
+
const preview = await callTrpc(
|
|
2723
|
+
"issue.previewDraft",
|
|
2724
|
+
{
|
|
2725
|
+
publicationId,
|
|
2726
|
+
title,
|
|
2727
|
+
html,
|
|
2728
|
+
...plainText ? { plainText } : {},
|
|
2729
|
+
...styleProfile ? { styleProfile } : {}
|
|
2730
|
+
},
|
|
2731
|
+
options
|
|
2732
|
+
);
|
|
2733
|
+
return makeToolResult(
|
|
2734
|
+
`Draft preview generated for ${preview.publicationId}: ${preview.title}`,
|
|
2735
|
+
preview
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
if (toolName === "issue.delivery_progress") {
|
|
2739
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2740
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2741
|
+
const progress = await callTrpc(
|
|
2742
|
+
"issue.deliveryProgress",
|
|
2743
|
+
{
|
|
2744
|
+
publicationId,
|
|
2745
|
+
issueId
|
|
2746
|
+
},
|
|
2747
|
+
options,
|
|
2748
|
+
"query"
|
|
2749
|
+
);
|
|
2750
|
+
if (progress.delivery.isPreparing) {
|
|
2751
|
+
return makeToolResult(
|
|
2752
|
+
`Issue ${progress.issueId} is preparing delivery (status: ${progress.status})`,
|
|
2753
|
+
progress
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
return makeToolResult(
|
|
2757
|
+
`Issue ${progress.issueId} delivery: ${progress.delivery.processed}/${progress.delivery.total} processed (${progress.delivery.completionPercent}%)`,
|
|
2758
|
+
progress
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
if (toolName === "issue.wait_delivery") {
|
|
2762
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2763
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2764
|
+
const timeoutMs = Math.max(1e3, Math.min(3e5, Math.trunc(readOptionalNumber(args, "timeoutMs") ?? 6e4)));
|
|
2765
|
+
const pollIntervalMs = Math.max(250, Math.min(1e4, Math.trunc(readOptionalNumber(args, "pollIntervalMs") ?? 2e3)));
|
|
2766
|
+
const startedAt = Date.now();
|
|
2767
|
+
while (true) {
|
|
2768
|
+
const progress = await callTrpc(
|
|
2769
|
+
"issue.deliveryProgress",
|
|
2770
|
+
{
|
|
2771
|
+
publicationId,
|
|
2772
|
+
issueId
|
|
2773
|
+
},
|
|
2774
|
+
options,
|
|
2775
|
+
"query"
|
|
2776
|
+
);
|
|
2777
|
+
if (progress.status === "sent" || progress.status === "failed") {
|
|
2778
|
+
return makeToolResult(
|
|
2779
|
+
`Issue ${progress.issueId} delivery finished with status ${progress.status}: ${progress.delivery.processed}/${progress.delivery.total} processed (${progress.delivery.completionPercent}%)`,
|
|
2780
|
+
{
|
|
2781
|
+
timedOut: false,
|
|
2782
|
+
elapsedMs: Date.now() - startedAt,
|
|
2783
|
+
progress
|
|
2784
|
+
}
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2788
|
+
if (elapsedMs >= timeoutMs) {
|
|
2789
|
+
return makeToolResult(
|
|
2790
|
+
`Issue ${progress.issueId} delivery still in status ${progress.status} after ${elapsedMs}ms`,
|
|
2791
|
+
{
|
|
2792
|
+
timedOut: true,
|
|
2793
|
+
elapsedMs,
|
|
2794
|
+
progress
|
|
2795
|
+
}
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
await sleep(pollIntervalMs);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
if (toolName === "analytics.poll_results") {
|
|
2802
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2803
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2804
|
+
const analytics = await callTrpc(
|
|
2805
|
+
"issue.pollResults",
|
|
2806
|
+
{
|
|
2807
|
+
publicationId,
|
|
2808
|
+
issueId
|
|
2809
|
+
},
|
|
2810
|
+
options,
|
|
2811
|
+
"query"
|
|
2812
|
+
);
|
|
2813
|
+
return makeToolResult(
|
|
2814
|
+
analytics.polls.length === 0 ? `No poll results yet for ${issueId}` : `Loaded ${analytics.polls.length} polls for ${issueId}`,
|
|
2815
|
+
analytics
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
if (toolName === "analytics.issue_performance") {
|
|
2819
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2820
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2821
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "all";
|
|
2822
|
+
const analytics = await callTrpc(
|
|
2823
|
+
"issue.analytics",
|
|
2824
|
+
{
|
|
2825
|
+
publicationId,
|
|
2826
|
+
issueId,
|
|
2827
|
+
range
|
|
2828
|
+
},
|
|
2829
|
+
options,
|
|
2830
|
+
"query"
|
|
2831
|
+
);
|
|
2832
|
+
return makeToolResult(
|
|
2833
|
+
`Issue analytics loaded for ${issueId}: ${analytics.opens.unique} unique opens, ${analytics.clicks.unique} unique clicks`,
|
|
2834
|
+
analytics
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
if (toolName === "analytics.issue_export_csv") {
|
|
2838
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2839
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2840
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "all";
|
|
2841
|
+
const exportType = readIssueAnalyticsExportType(args, "exportType") ?? "combined";
|
|
2842
|
+
const exported = await callTrpc(
|
|
2843
|
+
"issue.analyticsExportCsv",
|
|
2844
|
+
{
|
|
2845
|
+
publicationId,
|
|
2846
|
+
issueId,
|
|
2847
|
+
range,
|
|
2848
|
+
exportType
|
|
2849
|
+
},
|
|
2850
|
+
options,
|
|
2851
|
+
"query"
|
|
2852
|
+
);
|
|
2853
|
+
return makeToolResult(
|
|
2854
|
+
`Issue analytics ${exported.exportType} CSV ready for ${issueId}: ${exported.filename} (${exported.rowCount} rows)`,
|
|
2855
|
+
exported
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
if (toolName === "analytics.issue_export_performance_csv") {
|
|
2859
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2860
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2861
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "all";
|
|
2862
|
+
const exported = await callTrpc(
|
|
2863
|
+
"issue.analyticsExportCsv",
|
|
2864
|
+
{
|
|
2865
|
+
publicationId,
|
|
2866
|
+
issueId,
|
|
2867
|
+
range,
|
|
2868
|
+
exportType: "performance"
|
|
2869
|
+
},
|
|
2870
|
+
options,
|
|
2871
|
+
"query"
|
|
2872
|
+
);
|
|
2873
|
+
return makeToolResult(
|
|
2874
|
+
`Issue performance CSV ready for ${issueId}: ${exported.filename} (${exported.rowCount} rows)`,
|
|
2875
|
+
exported
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
if (toolName === "analytics.issue_export_polls_csv") {
|
|
2879
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2880
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2881
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "all";
|
|
2882
|
+
const exported = await callTrpc(
|
|
2883
|
+
"issue.analyticsExportCsv",
|
|
2884
|
+
{
|
|
2885
|
+
publicationId,
|
|
2886
|
+
issueId,
|
|
2887
|
+
range,
|
|
2888
|
+
exportType: "polls"
|
|
2889
|
+
},
|
|
2890
|
+
options,
|
|
2891
|
+
"query"
|
|
2892
|
+
);
|
|
2893
|
+
return makeToolResult(
|
|
2894
|
+
`Issue poll CSV ready for ${issueId}: ${exported.filename} (${exported.rowCount} rows)`,
|
|
2895
|
+
exported
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
if (toolName === "analytics.issue_trend") {
|
|
2899
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
2900
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2901
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "all";
|
|
2902
|
+
const trend = await callTrpc(
|
|
2903
|
+
"issue.analyticsTrend",
|
|
2904
|
+
{
|
|
2905
|
+
publicationId,
|
|
2906
|
+
issueId,
|
|
2907
|
+
range
|
|
2908
|
+
},
|
|
2909
|
+
options,
|
|
2910
|
+
"query"
|
|
2911
|
+
);
|
|
2912
|
+
return makeToolResult(
|
|
2913
|
+
`Issue trend loaded for ${issueId}: ${trend.points.length} daily points`,
|
|
2914
|
+
trend
|
|
2915
|
+
);
|
|
2916
|
+
}
|
|
2917
|
+
if (toolName === "analytics.latest_summary") {
|
|
2918
|
+
const publicationId = asOptionalString(args.publicationId);
|
|
2919
|
+
const range = readIssueAnalyticsRange(args, "range") ?? "7d";
|
|
2920
|
+
const summary = await loadLatestAnalyticsSummary(options, {
|
|
2921
|
+
publicationId,
|
|
2922
|
+
range
|
|
2923
|
+
});
|
|
2924
|
+
if (summary.status === "missing_publication_context") {
|
|
2925
|
+
return makeToolResult("Missing publication context for analytics.latest_summary", summary);
|
|
2926
|
+
}
|
|
2927
|
+
if (summary.status === "no_issues") {
|
|
2928
|
+
return makeToolResult(`No issues found for ${summary.publicationId}`, summary);
|
|
2929
|
+
}
|
|
2930
|
+
return makeToolResult(
|
|
2931
|
+
`Latest analytics loaded for ${summary.issue.id}: ${summary.analytics.opens.unique} unique opens, ${summary.analytics.clicks.unique} unique clicks`,
|
|
2932
|
+
summary
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2935
|
+
if (toolName === "issue.schedule") {
|
|
2936
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2937
|
+
const scheduledFor = asOptionalString(args.scheduledFor);
|
|
2938
|
+
const issue = await callTrpc(
|
|
2939
|
+
"issue.schedule",
|
|
2940
|
+
{
|
|
2941
|
+
issueId,
|
|
2942
|
+
...scheduledFor ? { scheduledFor } : {}
|
|
2943
|
+
},
|
|
2944
|
+
options
|
|
2945
|
+
);
|
|
2946
|
+
return makeToolResult(`Issue scheduled: ${issue.id}`, { issue });
|
|
2947
|
+
}
|
|
2948
|
+
if (toolName === "issue.unschedule") {
|
|
2949
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2950
|
+
const issue = await callTrpc(
|
|
2951
|
+
"issue.unschedule",
|
|
2952
|
+
{ issueId },
|
|
2953
|
+
options
|
|
2954
|
+
);
|
|
2955
|
+
return makeToolResult(`Issue unscheduled: ${issue.id}`, { issue });
|
|
2956
|
+
}
|
|
2957
|
+
if (toolName === "issue.publish_to_web") {
|
|
2958
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2959
|
+
const issue = await callTrpc(
|
|
2960
|
+
"issue.publishToWeb",
|
|
2961
|
+
{ issueId },
|
|
2962
|
+
options
|
|
2963
|
+
);
|
|
2964
|
+
return makeToolResult(`Issue published to web: ${issue.id}`, { issue });
|
|
2965
|
+
}
|
|
2966
|
+
if (toolName === "issue.unpublish_from_web") {
|
|
2967
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2968
|
+
const issue = await callTrpc(
|
|
2969
|
+
"issue.unpublishFromWeb",
|
|
2970
|
+
{ issueId },
|
|
2971
|
+
options
|
|
2972
|
+
);
|
|
2973
|
+
return makeToolResult(`Issue unpublished from web: ${issue.id}`, { issue });
|
|
2974
|
+
}
|
|
2975
|
+
if (toolName === "issue.send_now") {
|
|
2976
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2977
|
+
const issue = await callTrpc(
|
|
2978
|
+
"issue.sendNow",
|
|
2979
|
+
{ issueId },
|
|
2980
|
+
options
|
|
2981
|
+
);
|
|
2982
|
+
return makeToolResult(`Issue sent: ${issue.id}`, { issue });
|
|
2983
|
+
}
|
|
2984
|
+
if (toolName === "issue.send_and_wait") {
|
|
2985
|
+
const issueId = readRequiredString(args, "issueId");
|
|
2986
|
+
const timeoutMs = Math.max(1e3, Math.min(3e5, Math.trunc(readOptionalNumber(args, "timeoutMs") ?? 6e4)));
|
|
2987
|
+
const pollIntervalMs = Math.max(250, Math.min(1e4, Math.trunc(readOptionalNumber(args, "pollIntervalMs") ?? 2e3)));
|
|
2988
|
+
const issue = await callTrpc(
|
|
2989
|
+
"issue.sendNow",
|
|
2990
|
+
{ issueId },
|
|
2991
|
+
options
|
|
2992
|
+
);
|
|
2993
|
+
const startedAt = Date.now();
|
|
2994
|
+
while (true) {
|
|
2995
|
+
const progress = await callTrpc(
|
|
2996
|
+
"issue.deliveryProgress",
|
|
2997
|
+
{
|
|
2998
|
+
publicationId: issue.publicationId,
|
|
2999
|
+
issueId: issue.id
|
|
3000
|
+
},
|
|
3001
|
+
options,
|
|
3002
|
+
"query"
|
|
3003
|
+
);
|
|
3004
|
+
if (progress.status === "sent" || progress.status === "failed") {
|
|
3005
|
+
return makeToolResult(
|
|
3006
|
+
`Issue ${issue.id} send-and-wait finished with status ${progress.status}: ${progress.delivery.processed}/${progress.delivery.total} processed (${progress.delivery.completionPercent}%)`,
|
|
3007
|
+
{
|
|
3008
|
+
issue,
|
|
3009
|
+
timedOut: false,
|
|
3010
|
+
elapsedMs: Date.now() - startedAt,
|
|
3011
|
+
progress
|
|
3012
|
+
}
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
const elapsedMs = Date.now() - startedAt;
|
|
3016
|
+
if (elapsedMs >= timeoutMs) {
|
|
3017
|
+
return makeToolResult(
|
|
3018
|
+
`Issue ${issue.id} queued but still ${progress.status} after ${elapsedMs}ms`,
|
|
3019
|
+
{
|
|
3020
|
+
issue,
|
|
3021
|
+
timedOut: true,
|
|
3022
|
+
elapsedMs,
|
|
3023
|
+
progress
|
|
3024
|
+
}
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
await sleep(pollIntervalMs);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (toolName === "ai.generate_draft") {
|
|
3031
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3032
|
+
const prompt = readRequiredString(args, "prompt");
|
|
3033
|
+
const tone = asOptionalString(args.tone);
|
|
3034
|
+
if (tone && tone !== "neutral" && tone !== "friendly" && tone !== "formal") {
|
|
3035
|
+
throw new Error("tone must be one of: neutral, friendly, formal");
|
|
3036
|
+
}
|
|
3037
|
+
const draft = await callTrpc(
|
|
3038
|
+
"ai.generateDraft",
|
|
3039
|
+
{
|
|
3040
|
+
publicationId,
|
|
3041
|
+
prompt,
|
|
3042
|
+
...tone ? { tone } : {}
|
|
3043
|
+
},
|
|
3044
|
+
options
|
|
3045
|
+
);
|
|
3046
|
+
return makeToolResult("AI draft generated", draft);
|
|
3047
|
+
}
|
|
3048
|
+
if (toolName === "template.create") {
|
|
3049
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3050
|
+
const name = readRequiredString(args, "name");
|
|
3051
|
+
const html = asOptionalString(args.html);
|
|
3052
|
+
const spec = args.spec;
|
|
3053
|
+
const description = asOptionalString(args.description);
|
|
3054
|
+
const subject = asOptionalString(args.subject);
|
|
3055
|
+
const variables = args.variables;
|
|
3056
|
+
if (!html && !spec) {
|
|
3057
|
+
throw new Error("Either 'html' or 'spec' must be provided");
|
|
3058
|
+
}
|
|
3059
|
+
const body = {
|
|
3060
|
+
publication_id: publicationId,
|
|
3061
|
+
name,
|
|
3062
|
+
...html ? { html } : {},
|
|
3063
|
+
...spec ? { spec } : {},
|
|
3064
|
+
...description ? { description } : {},
|
|
3065
|
+
...subject ? { subject } : {},
|
|
3066
|
+
...variables ? { variables } : {}
|
|
3067
|
+
};
|
|
3068
|
+
const template = await callRestApi(
|
|
3069
|
+
"POST",
|
|
3070
|
+
"/v1/templates",
|
|
3071
|
+
body,
|
|
3072
|
+
options
|
|
3073
|
+
);
|
|
3074
|
+
return makeToolResult(
|
|
3075
|
+
`Template created: ${template.id} (${template.format})`,
|
|
3076
|
+
{ template }
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3079
|
+
if (toolName === "template.list") {
|
|
3080
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3081
|
+
const limit = readOptionalNumber(args, "limit") ?? 20;
|
|
3082
|
+
const result = await callRestApi(
|
|
3083
|
+
"GET",
|
|
3084
|
+
`/v1/templates?publication_id=${encodeURIComponent(publicationId)}&limit=${limit}`,
|
|
3085
|
+
void 0,
|
|
3086
|
+
options
|
|
3087
|
+
);
|
|
3088
|
+
const names = result.data.map((t) => `${t.id}: ${t.name} (${t.format}, ${t.status})`);
|
|
3089
|
+
return makeToolResult(
|
|
3090
|
+
result.data.length > 0 ? `${result.data.length} template(s):
|
|
3091
|
+
${names.join("\n")}` : "No templates found",
|
|
3092
|
+
result
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
if (toolName === "template.get") {
|
|
3096
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3097
|
+
const templateId = readRequiredString(args, "templateId");
|
|
3098
|
+
const template = await callRestApi(
|
|
3099
|
+
"GET",
|
|
3100
|
+
`/v1/templates/${encodeURIComponent(templateId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3101
|
+
void 0,
|
|
3102
|
+
options
|
|
3103
|
+
);
|
|
3104
|
+
return makeToolResult(
|
|
3105
|
+
`Template: ${template.name} (${template.format}, ${template.status})`,
|
|
3106
|
+
{ template }
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
if (toolName === "template.update") {
|
|
3110
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3111
|
+
const templateId = readRequiredString(args, "templateId");
|
|
3112
|
+
const name = asOptionalString(args.name);
|
|
3113
|
+
const html = asOptionalString(args.html);
|
|
3114
|
+
const spec = args.spec;
|
|
3115
|
+
const description = asOptionalString(args.description);
|
|
3116
|
+
const text = asOptionalString(args.text);
|
|
3117
|
+
const subject = asOptionalString(args.subject);
|
|
3118
|
+
const from = asOptionalString(args.from);
|
|
3119
|
+
const replyTo = asOptionalString(args.reply_to);
|
|
3120
|
+
const variables = args.variables;
|
|
3121
|
+
const body = {
|
|
3122
|
+
...name ? { name } : {},
|
|
3123
|
+
...html ? { html } : {},
|
|
3124
|
+
...spec ? { spec } : {},
|
|
3125
|
+
...description ? { description } : {},
|
|
3126
|
+
...text ? { text } : {},
|
|
3127
|
+
...subject ? { subject } : {},
|
|
3128
|
+
...from ? { from } : {},
|
|
3129
|
+
...replyTo ? { reply_to: replyTo } : {},
|
|
3130
|
+
...variables ? { variables } : {}
|
|
3131
|
+
};
|
|
3132
|
+
const template = await callRestApi(
|
|
3133
|
+
"PATCH",
|
|
3134
|
+
`/v1/templates/${encodeURIComponent(templateId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3135
|
+
body,
|
|
3136
|
+
options
|
|
3137
|
+
);
|
|
3138
|
+
return makeToolResult(`Template updated: ${template.id}`, { template });
|
|
3139
|
+
}
|
|
3140
|
+
if (toolName === "template.publish") {
|
|
3141
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3142
|
+
const templateId = readRequiredString(args, "templateId");
|
|
3143
|
+
const template = await callRestApi(
|
|
3144
|
+
"POST",
|
|
3145
|
+
`/v1/templates/${encodeURIComponent(templateId)}/publish?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3146
|
+
void 0,
|
|
3147
|
+
options
|
|
3148
|
+
);
|
|
3149
|
+
return makeToolResult(`Template published: ${template.id} (${template.status})`, { template });
|
|
3150
|
+
}
|
|
3151
|
+
if (toolName === "template.duplicate") {
|
|
3152
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3153
|
+
const templateId = readRequiredString(args, "templateId");
|
|
3154
|
+
const template = await callRestApi(
|
|
3155
|
+
"POST",
|
|
3156
|
+
`/v1/templates/${encodeURIComponent(templateId)}/duplicate?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3157
|
+
void 0,
|
|
3158
|
+
options
|
|
3159
|
+
);
|
|
3160
|
+
return makeToolResult(`Template duplicated: new template ${template.id}`, { template });
|
|
3161
|
+
}
|
|
3162
|
+
if (toolName === "template.delete") {
|
|
3163
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3164
|
+
const templateId = readRequiredString(args, "templateId");
|
|
3165
|
+
const result = await callRestApi(
|
|
3166
|
+
"DELETE",
|
|
3167
|
+
`/v1/templates/${encodeURIComponent(templateId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3168
|
+
void 0,
|
|
3169
|
+
options
|
|
3170
|
+
);
|
|
3171
|
+
return makeToolResult(`Template deleted: ${result.id}`, result);
|
|
3172
|
+
}
|
|
3173
|
+
if (toolName === "email.send") {
|
|
3174
|
+
const body = {};
|
|
3175
|
+
for (const key of EMAIL_SEND_FIELDS) {
|
|
3176
|
+
if (args[key] !== void 0) body[key] = args[key];
|
|
3177
|
+
}
|
|
3178
|
+
for (const required of ["from", "to", "subject"]) {
|
|
3179
|
+
if (body[required] === void 0) {
|
|
3180
|
+
throw new Error(`Missing required field: ${required}`);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
const result = await callRestApi(
|
|
3184
|
+
"POST",
|
|
3185
|
+
"/v1/emails",
|
|
3186
|
+
body,
|
|
3187
|
+
options
|
|
3188
|
+
);
|
|
3189
|
+
return makeToolResult(
|
|
3190
|
+
body.scheduled_at ? `Email scheduled (${result.id}) for ${String(body.scheduled_at)}` : `Email queued for delivery (${result.id})`,
|
|
3191
|
+
result
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
if (toolName === "email.batch") {
|
|
3195
|
+
const emails = args.emails;
|
|
3196
|
+
if (!Array.isArray(emails) || emails.length === 0) {
|
|
3197
|
+
throw new Error("'emails' must be a non-empty array of email objects.");
|
|
3198
|
+
}
|
|
3199
|
+
const result = await callRestApi(
|
|
3200
|
+
"POST",
|
|
3201
|
+
"/v1/emails/batch",
|
|
3202
|
+
emails,
|
|
3203
|
+
options
|
|
3204
|
+
);
|
|
3205
|
+
return makeToolResult(
|
|
3206
|
+
`Batch queued: ${result.data?.length ?? 0} email(s).`,
|
|
3207
|
+
result
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
if (toolName === "email.get") {
|
|
3211
|
+
const id = readRequiredString(args, "id");
|
|
3212
|
+
const email = await callRestApi(
|
|
3213
|
+
"GET",
|
|
3214
|
+
`/v1/emails/${encodeURIComponent(id)}`,
|
|
3215
|
+
void 0,
|
|
3216
|
+
options
|
|
3217
|
+
);
|
|
3218
|
+
return makeToolResult(
|
|
3219
|
+
`Email ${id}: ${String(email.last_event ?? "unknown")} (opens ${String(
|
|
3220
|
+
email.open_count ?? 0
|
|
3221
|
+
)}, clicks ${String(email.click_count ?? 0)})`,
|
|
3222
|
+
{ email }
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
if (toolName === "email.reschedule") {
|
|
3226
|
+
const id = readRequiredString(args, "id");
|
|
3227
|
+
const scheduledAt = readRequiredString(args, "scheduled_at");
|
|
3228
|
+
const result = await callRestApi(
|
|
3229
|
+
"PATCH",
|
|
3230
|
+
`/v1/emails/${encodeURIComponent(id)}`,
|
|
3231
|
+
{ scheduled_at: scheduledAt },
|
|
3232
|
+
options
|
|
3233
|
+
);
|
|
3234
|
+
return makeToolResult(`Email ${id} rescheduled for ${scheduledAt}.`, result);
|
|
3235
|
+
}
|
|
3236
|
+
if (toolName === "email.cancel") {
|
|
3237
|
+
const id = readRequiredString(args, "id");
|
|
3238
|
+
const result = await callRestApi(
|
|
3239
|
+
"POST",
|
|
3240
|
+
`/v1/emails/${encodeURIComponent(id)}/cancel`,
|
|
3241
|
+
void 0,
|
|
3242
|
+
options
|
|
3243
|
+
);
|
|
3244
|
+
return makeToolResult(`Email ${id} canceled.`, result);
|
|
3245
|
+
}
|
|
3246
|
+
if (toolName === "email.resend") {
|
|
3247
|
+
const id = readRequiredString(args, "id");
|
|
3248
|
+
const result = await callTrpc(
|
|
3249
|
+
"email.resend",
|
|
3250
|
+
{ id },
|
|
3251
|
+
options
|
|
3252
|
+
);
|
|
3253
|
+
return makeToolResult(`Email ${id} resent as ${result.email.id}.`, result);
|
|
3254
|
+
}
|
|
3255
|
+
if (toolName === "email.list") {
|
|
3256
|
+
const params = new URLSearchParams();
|
|
3257
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3258
|
+
const offset = readOptionalNumber(args, "offset");
|
|
3259
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3260
|
+
if (offset !== void 0) params.set("offset", String(offset));
|
|
3261
|
+
for (const key of ["status", "tag_name", "tag_value", "from_date", "to_date"]) {
|
|
3262
|
+
const value = asOptionalString(args[key]);
|
|
3263
|
+
if (value) params.set(key, value);
|
|
3264
|
+
}
|
|
3265
|
+
const qs = params.toString();
|
|
3266
|
+
const result = await callRestApi("GET", `/v1/emails${qs ? `?${qs}` : ""}`, void 0, options);
|
|
3267
|
+
const lines = result.data.map(
|
|
3268
|
+
(email) => `${String(email.id)}: ${String(email.last_event)} \u2014 ${String(email.subject)} \u2192 ${String(email.to)}`
|
|
3269
|
+
);
|
|
3270
|
+
return makeToolResult(
|
|
3271
|
+
result.data.length > 0 ? `${result.data.length} of ${result.total} email(s):
|
|
3272
|
+
${lines.join("\n")}` : "No emails found.",
|
|
3273
|
+
result
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
if (toolName === "email.analytics") {
|
|
3277
|
+
const params = new URLSearchParams();
|
|
3278
|
+
for (const key of ["from_date", "to_date"]) {
|
|
3279
|
+
const value = asOptionalString(args[key]);
|
|
3280
|
+
if (value) params.set(key, value);
|
|
3281
|
+
}
|
|
3282
|
+
const qs = params.toString();
|
|
3283
|
+
const result = await callRestApi("GET", `/v1/emails/analytics${qs ? `?${qs}` : ""}`, void 0, options);
|
|
3284
|
+
const pct = (rate) => `${(rate * 100).toFixed(1)}%`;
|
|
3285
|
+
return makeToolResult(
|
|
3286
|
+
`${result.sent} sent (of ${result.total}): delivered ${result.delivered} (${pct(result.rates.delivery_rate)}), opened ${result.opened} (${pct(result.rates.open_rate)}), clicked ${result.clicked} (${pct(result.rates.click_rate)}), bounced ${result.bounced} (${pct(result.rates.bounce_rate)})`,
|
|
3287
|
+
result
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
if (toolName === "email.inbound_list") {
|
|
3291
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3292
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3293
|
+
const cursor = asOptionalString(args.cursor);
|
|
3294
|
+
const params = new URLSearchParams({ publication_id: publicationId });
|
|
3295
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3296
|
+
if (cursor) params.set("cursor", cursor);
|
|
3297
|
+
const result = await callRestApi(
|
|
3298
|
+
"GET",
|
|
3299
|
+
`/v1/emails/inbound?${params.toString()}`,
|
|
3300
|
+
void 0,
|
|
3301
|
+
options
|
|
3302
|
+
);
|
|
3303
|
+
return makeToolResult(`${result.data.length} inbound email(s).`, result);
|
|
3304
|
+
}
|
|
3305
|
+
if (toolName === "email.inbound_get") {
|
|
3306
|
+
const id = readRequiredString(args, "id");
|
|
3307
|
+
const result = await callRestApi(
|
|
3308
|
+
"GET",
|
|
3309
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}`,
|
|
3310
|
+
void 0,
|
|
3311
|
+
options
|
|
3312
|
+
);
|
|
3313
|
+
return makeToolResult(
|
|
3314
|
+
`Inbound email ${id}: ${String(result.subject ?? "(no subject)")} from ${String(
|
|
3315
|
+
result.from ?? "unknown"
|
|
3316
|
+
)}.`,
|
|
3317
|
+
result
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
if (toolName === "email.inbound_list_attachments") {
|
|
3321
|
+
const id = readRequiredString(args, "id");
|
|
3322
|
+
const result = await callRestApi(
|
|
3323
|
+
"GET",
|
|
3324
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/attachments`,
|
|
3325
|
+
void 0,
|
|
3326
|
+
options
|
|
3327
|
+
);
|
|
3328
|
+
return makeToolResult(`${result.data.length} attachment(s).`, result);
|
|
3329
|
+
}
|
|
3330
|
+
if (toolName === "email.inbound_get_attachment") {
|
|
3331
|
+
const id = readRequiredString(args, "id");
|
|
3332
|
+
const attachmentId = readRequiredString(args, "attachmentId");
|
|
3333
|
+
const result = await callRestApi(
|
|
3334
|
+
"GET",
|
|
3335
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
3336
|
+
void 0,
|
|
3337
|
+
options
|
|
3338
|
+
);
|
|
3339
|
+
return makeToolResult(`Attachment ${result.id} (${result.filename}).`, result);
|
|
3340
|
+
}
|
|
3341
|
+
if (toolName === "email.inbound_reply") {
|
|
3342
|
+
const id = readRequiredString(args, "id");
|
|
3343
|
+
const body = {};
|
|
3344
|
+
for (const key of ["from", "subject", "html", "text", "cc", "bcc", "idempotency_key"]) {
|
|
3345
|
+
if (args[key] !== void 0) body[key] = args[key];
|
|
3346
|
+
}
|
|
3347
|
+
if (body.html === void 0 && body.text === void 0) {
|
|
3348
|
+
throw new Error("Provide 'html' and/or 'text' for the reply body.");
|
|
3349
|
+
}
|
|
3350
|
+
const result = await callRestApi(
|
|
3351
|
+
"POST",
|
|
3352
|
+
`/v1/emails/inbound/${encodeURIComponent(id)}/reply`,
|
|
3353
|
+
body,
|
|
3354
|
+
options
|
|
3355
|
+
);
|
|
3356
|
+
return makeToolResult(`Reply queued as ${result.id} (${result.status}).`, result);
|
|
3357
|
+
}
|
|
3358
|
+
if (toolName === "issue.send_test") {
|
|
3359
|
+
const issueId = readRequiredString(args, "issueId");
|
|
3360
|
+
const from = readRequiredString(args, "from");
|
|
3361
|
+
const recipients = args.recipients;
|
|
3362
|
+
if (!recipients || typeof recipients !== "string" && !Array.isArray(recipients) || Array.isArray(recipients) && recipients.length === 0) {
|
|
3363
|
+
throw new Error("'recipients' must be an email address or a non-empty array of addresses.");
|
|
3364
|
+
}
|
|
3365
|
+
const recipientList = Array.isArray(recipients) ? recipients : [recipients];
|
|
3366
|
+
const result = await callRestApi(
|
|
3367
|
+
"POST",
|
|
3368
|
+
`/v1/posts/${encodeURIComponent(issueId)}/test`,
|
|
3369
|
+
{ recipients: recipientList, from },
|
|
3370
|
+
options
|
|
3371
|
+
);
|
|
3372
|
+
const failedNote = result.failed_to.length ? `, ${result.failed_to.length} failed` : "";
|
|
3373
|
+
return makeToolResult(
|
|
3374
|
+
`Test of ${issueId} sent to ${result.sent_to.length} recipient(s)${failedNote}.`,
|
|
3375
|
+
result
|
|
3376
|
+
);
|
|
3377
|
+
}
|
|
3378
|
+
if (toolName === "domain.create") {
|
|
3379
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3380
|
+
const name = readRequiredString(args, "name");
|
|
3381
|
+
const purpose = asOptionalString(args.purpose);
|
|
3382
|
+
const isPrimary = readOptionalBoolean(args, "is_primary");
|
|
3383
|
+
const result = await callRestApi(
|
|
3384
|
+
"POST",
|
|
3385
|
+
"/v1/domains",
|
|
3386
|
+
{
|
|
3387
|
+
publication_id: publicationId,
|
|
3388
|
+
name,
|
|
3389
|
+
...purpose ? { purpose } : {},
|
|
3390
|
+
...isPrimary !== void 0 ? { is_primary: isPrimary } : {}
|
|
3391
|
+
},
|
|
3392
|
+
options
|
|
3393
|
+
);
|
|
3394
|
+
return makeToolResult(
|
|
3395
|
+
`Domain ${result.name} created (${result.status}). Add the DNS records, then call domain.verify.`,
|
|
3396
|
+
result
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
if (toolName === "domain.list") {
|
|
3400
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3401
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3402
|
+
const params = new URLSearchParams({ publication_id: publicationId });
|
|
3403
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3404
|
+
const result = await callRestApi(
|
|
3405
|
+
"GET",
|
|
3406
|
+
`/v1/domains?${params.toString()}`,
|
|
3407
|
+
void 0,
|
|
3408
|
+
options
|
|
3409
|
+
);
|
|
3410
|
+
return makeToolResult(`${result.data.length} domain(s).`, result);
|
|
3411
|
+
}
|
|
3412
|
+
if (toolName === "domain.get") {
|
|
3413
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3414
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3415
|
+
const result = await callRestApi(
|
|
3416
|
+
"GET",
|
|
3417
|
+
`/v1/domains/${encodeURIComponent(domainId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3418
|
+
void 0,
|
|
3419
|
+
options
|
|
3420
|
+
);
|
|
3421
|
+
return makeToolResult(`Domain ${result.name} (${result.status}).`, result);
|
|
3422
|
+
}
|
|
3423
|
+
if (toolName === "domain.verify") {
|
|
3424
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3425
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3426
|
+
const result = await callRestApi(
|
|
3427
|
+
"POST",
|
|
3428
|
+
`/v1/domains/${encodeURIComponent(domainId)}/verify?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3429
|
+
void 0,
|
|
3430
|
+
options
|
|
3431
|
+
);
|
|
3432
|
+
return makeToolResult(`Domain ${result.name} is now ${result.status}.`, result);
|
|
3433
|
+
}
|
|
3434
|
+
if (toolName === "domain.update") {
|
|
3435
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3436
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3437
|
+
const purpose = asOptionalString(args.purpose);
|
|
3438
|
+
const isPrimary = readOptionalBoolean(args, "is_primary");
|
|
3439
|
+
const result = await callRestApi(
|
|
3440
|
+
"PATCH",
|
|
3441
|
+
`/v1/domains/${encodeURIComponent(domainId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3442
|
+
{
|
|
3443
|
+
...purpose ? { purpose } : {},
|
|
3444
|
+
...isPrimary !== void 0 ? { is_primary: isPrimary } : {}
|
|
3445
|
+
},
|
|
3446
|
+
options
|
|
3447
|
+
);
|
|
3448
|
+
return makeToolResult(`Domain ${result.name} updated (purpose: ${result.purpose}).`, result);
|
|
3449
|
+
}
|
|
3450
|
+
if (toolName === "domain.delete") {
|
|
3451
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3452
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3453
|
+
const result = await callRestApi(
|
|
3454
|
+
"DELETE",
|
|
3455
|
+
`/v1/domains/${encodeURIComponent(domainId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3456
|
+
void 0,
|
|
3457
|
+
options
|
|
3458
|
+
);
|
|
3459
|
+
return makeToolResult(`Domain ${result.id} deleted.`, result);
|
|
3460
|
+
}
|
|
3461
|
+
if (toolName === "domain.tracking_create") {
|
|
3462
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3463
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3464
|
+
const subdomain = readRequiredString(args, "subdomain");
|
|
3465
|
+
const result = await callRestApi(
|
|
3466
|
+
"POST",
|
|
3467
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3468
|
+
{ subdomain },
|
|
3469
|
+
options
|
|
3470
|
+
);
|
|
3471
|
+
return makeToolResult(
|
|
3472
|
+
`Tracking domain ${result.full_name} created (${result.status}). Add the CNAME record, then call domain.tracking_verify.`,
|
|
3473
|
+
result
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
if (toolName === "domain.tracking_list") {
|
|
3477
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3478
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3479
|
+
const result = await callRestApi(
|
|
3480
|
+
"GET",
|
|
3481
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3482
|
+
void 0,
|
|
3483
|
+
options
|
|
3484
|
+
);
|
|
3485
|
+
return makeToolResult(`${result.data.length} tracking domain(s).`, result);
|
|
3486
|
+
}
|
|
3487
|
+
if (toolName === "domain.tracking_verify") {
|
|
3488
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3489
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3490
|
+
const trackingDomainId = readRequiredString(args, "trackingDomainId");
|
|
3491
|
+
const result = await callRestApi(
|
|
3492
|
+
"POST",
|
|
3493
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains/${encodeURIComponent(trackingDomainId)}/verify?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3494
|
+
void 0,
|
|
3495
|
+
options
|
|
3496
|
+
);
|
|
3497
|
+
return makeToolResult(`Tracking domain ${result.full_name} is now ${result.status}.`, result);
|
|
3498
|
+
}
|
|
3499
|
+
if (toolName === "domain.tracking_delete") {
|
|
3500
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3501
|
+
const domainId = readRequiredString(args, "domainId");
|
|
3502
|
+
const trackingDomainId = readRequiredString(args, "trackingDomainId");
|
|
3503
|
+
const result = await callRestApi(
|
|
3504
|
+
"DELETE",
|
|
3505
|
+
`/v1/domains/${encodeURIComponent(domainId)}/tracking-domains/${encodeURIComponent(trackingDomainId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3506
|
+
void 0,
|
|
3507
|
+
options
|
|
3508
|
+
);
|
|
3509
|
+
return makeToolResult(`Tracking domain ${result.id} deleted.`, result);
|
|
3510
|
+
}
|
|
3511
|
+
if (toolName === "webhook.create") {
|
|
3512
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3513
|
+
const endpoint = readRequiredString(args, "endpoint");
|
|
3514
|
+
const events = args.events;
|
|
3515
|
+
if (!Array.isArray(events) || events.length === 0 || !events.every((e) => typeof e === "string")) {
|
|
3516
|
+
throw new Error("'events' must be a non-empty array of event-type strings.");
|
|
3517
|
+
}
|
|
3518
|
+
const result = await callRestApi(
|
|
3519
|
+
"POST",
|
|
3520
|
+
"/v1/webhooks/endpoints",
|
|
3521
|
+
{ publication_id: publicationId, endpoint, events },
|
|
3522
|
+
options
|
|
3523
|
+
);
|
|
3524
|
+
return makeToolResult(
|
|
3525
|
+
`Webhook ${result.id} created for ${result.endpoint}. Store the signing_secret now \u2014 it won't be shown again.`,
|
|
3526
|
+
result
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
if (toolName === "webhook.list") {
|
|
3530
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3531
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3532
|
+
const params = new URLSearchParams({ publication_id: publicationId });
|
|
3533
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3534
|
+
const result = await callRestApi(
|
|
3535
|
+
"GET",
|
|
3536
|
+
`/v1/webhooks/endpoints?${params.toString()}`,
|
|
3537
|
+
void 0,
|
|
3538
|
+
options
|
|
3539
|
+
);
|
|
3540
|
+
return makeToolResult(`${result.data.length} webhook(s).`, result);
|
|
3541
|
+
}
|
|
3542
|
+
if (toolName === "webhook.get") {
|
|
3543
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3544
|
+
const webhookId = readRequiredString(args, "webhookId");
|
|
3545
|
+
const result = await callRestApi(
|
|
3546
|
+
"GET",
|
|
3547
|
+
`/v1/webhooks/endpoints/${encodeURIComponent(webhookId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3548
|
+
void 0,
|
|
3549
|
+
options
|
|
3550
|
+
);
|
|
3551
|
+
return makeToolResult(`Webhook ${result.id} (${result.status}) -> ${result.endpoint}.`, result);
|
|
3552
|
+
}
|
|
3553
|
+
if (toolName === "webhook.update") {
|
|
3554
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3555
|
+
const webhookId = readRequiredString(args, "webhookId");
|
|
3556
|
+
const endpoint = asOptionalString(args.endpoint);
|
|
3557
|
+
const status = asOptionalString(args.status);
|
|
3558
|
+
const events = args.events;
|
|
3559
|
+
if (events !== void 0 && (!Array.isArray(events) || !events.every((e) => typeof e === "string"))) {
|
|
3560
|
+
throw new Error("'events' must be an array of event-type strings.");
|
|
3561
|
+
}
|
|
3562
|
+
const result = await callRestApi(
|
|
3563
|
+
"PATCH",
|
|
3564
|
+
`/v1/webhooks/endpoints/${encodeURIComponent(webhookId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3565
|
+
{
|
|
3566
|
+
...endpoint ? { endpoint } : {},
|
|
3567
|
+
...events !== void 0 ? { events } : {},
|
|
3568
|
+
...status ? { status } : {}
|
|
3569
|
+
},
|
|
3570
|
+
options
|
|
3571
|
+
);
|
|
3572
|
+
return makeToolResult(`Webhook ${result.id} updated (${result.status}).`, result);
|
|
3573
|
+
}
|
|
3574
|
+
if (toolName === "webhook.delete") {
|
|
3575
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3576
|
+
const webhookId = readRequiredString(args, "webhookId");
|
|
3577
|
+
const result = await callRestApi(
|
|
3578
|
+
"DELETE",
|
|
3579
|
+
`/v1/webhooks/endpoints/${encodeURIComponent(webhookId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3580
|
+
void 0,
|
|
3581
|
+
options
|
|
3582
|
+
);
|
|
3583
|
+
return makeToolResult(`Webhook ${result.id} deleted.`, result);
|
|
3584
|
+
}
|
|
3585
|
+
if (toolName === "segment.create") {
|
|
3586
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3587
|
+
const name = readRequiredString(args, "name");
|
|
3588
|
+
const description = asOptionalString(args.description);
|
|
3589
|
+
const statusFilter = asOptionalString(args.status_filter);
|
|
3590
|
+
const queryFilter = asOptionalString(args.query_filter);
|
|
3591
|
+
const result = await callRestApi(
|
|
3592
|
+
"POST",
|
|
3593
|
+
"/v1/segments",
|
|
3594
|
+
{
|
|
3595
|
+
publication_id: publicationId,
|
|
3596
|
+
name,
|
|
3597
|
+
...description !== void 0 ? { description } : {},
|
|
3598
|
+
...statusFilter ? { status_filter: statusFilter } : {},
|
|
3599
|
+
...queryFilter !== void 0 ? { query_filter: queryFilter } : {}
|
|
3600
|
+
},
|
|
3601
|
+
options
|
|
3602
|
+
);
|
|
3603
|
+
return makeToolResult(`Segment ${result.name} created (${result.id}).`, result);
|
|
3604
|
+
}
|
|
3605
|
+
if (toolName === "segment.list") {
|
|
3606
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3607
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3608
|
+
const params = new URLSearchParams({ publication_id: publicationId });
|
|
3609
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3610
|
+
const result = await callRestApi(
|
|
3611
|
+
"GET",
|
|
3612
|
+
`/v1/segments?${params.toString()}`,
|
|
3613
|
+
void 0,
|
|
3614
|
+
options
|
|
3615
|
+
);
|
|
3616
|
+
return makeToolResult(`${result.data.length} segment(s).`, result);
|
|
3617
|
+
}
|
|
3618
|
+
if (toolName === "segment.get") {
|
|
3619
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3620
|
+
const segmentId = readRequiredString(args, "segmentId");
|
|
3621
|
+
const result = await callRestApi(
|
|
3622
|
+
"GET",
|
|
3623
|
+
`/v1/segments/${encodeURIComponent(segmentId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3624
|
+
void 0,
|
|
3625
|
+
options
|
|
3626
|
+
);
|
|
3627
|
+
return makeToolResult(`Segment ${result.name} (${result.id}).`, result);
|
|
3628
|
+
}
|
|
3629
|
+
if (toolName === "segment.update") {
|
|
3630
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3631
|
+
const segmentId = readRequiredString(args, "segmentId");
|
|
3632
|
+
const name = asOptionalString(args.name);
|
|
3633
|
+
const description = asOptionalString(args.description);
|
|
3634
|
+
const result = await callRestApi(
|
|
3635
|
+
"PATCH",
|
|
3636
|
+
`/v1/segments/${encodeURIComponent(segmentId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3637
|
+
{
|
|
3638
|
+
...name ? { name } : {},
|
|
3639
|
+
...description !== void 0 ? { description } : {},
|
|
3640
|
+
// status_filter / query_filter are nullable: forward an explicit null
|
|
3641
|
+
// to CLEAR the filter, a value to set it, and omit the key entirely
|
|
3642
|
+
// (absent from args) to leave it unchanged.
|
|
3643
|
+
..."status_filter" in args ? { status_filter: args.status_filter } : {},
|
|
3644
|
+
..."query_filter" in args ? { query_filter: args.query_filter } : {}
|
|
3645
|
+
},
|
|
3646
|
+
options
|
|
3647
|
+
);
|
|
3648
|
+
return makeToolResult(`Segment ${result.id} updated.`, result);
|
|
3649
|
+
}
|
|
3650
|
+
if (toolName === "segment.delete") {
|
|
3651
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3652
|
+
const segmentId = readRequiredString(args, "segmentId");
|
|
3653
|
+
const result = await callRestApi(
|
|
3654
|
+
"DELETE",
|
|
3655
|
+
`/v1/segments/${encodeURIComponent(segmentId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3656
|
+
void 0,
|
|
3657
|
+
options
|
|
3658
|
+
);
|
|
3659
|
+
return makeToolResult(`Segment ${result.id} deleted.`, result);
|
|
3660
|
+
}
|
|
3661
|
+
if (toolName === "contact_property.create") {
|
|
3662
|
+
const key = readRequiredString(args, "key");
|
|
3663
|
+
const type = asOptionalString(args.type);
|
|
3664
|
+
if (type !== "string" && type !== "number") {
|
|
3665
|
+
throw new Error("'type' must be 'string' or 'number'.");
|
|
3666
|
+
}
|
|
3667
|
+
const description = asOptionalString(args.description);
|
|
3668
|
+
const fallback = args.fallback_value;
|
|
3669
|
+
const result = await callRestApi(
|
|
3670
|
+
"POST",
|
|
3671
|
+
"/v1/contact-properties",
|
|
3672
|
+
{
|
|
3673
|
+
key,
|
|
3674
|
+
type,
|
|
3675
|
+
...fallback !== void 0 ? { fallback_value: fallback } : {},
|
|
3676
|
+
...description !== void 0 ? { description } : {}
|
|
3677
|
+
},
|
|
3678
|
+
options
|
|
3679
|
+
);
|
|
3680
|
+
return makeToolResult(`Contact property ${result.key} created (${result.id}).`, result);
|
|
3681
|
+
}
|
|
3682
|
+
if (toolName === "contact_property.list") {
|
|
3683
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3684
|
+
const qs = limit !== void 0 ? `?limit=${limit}` : "";
|
|
3685
|
+
const result = await callRestApi(
|
|
3686
|
+
"GET",
|
|
3687
|
+
`/v1/contact-properties${qs}`,
|
|
3688
|
+
void 0,
|
|
3689
|
+
options
|
|
3690
|
+
);
|
|
3691
|
+
return makeToolResult(`${result.data.length} contact propert(ies).`, result);
|
|
3692
|
+
}
|
|
3693
|
+
if (toolName === "contact_property.update") {
|
|
3694
|
+
const propertyId = readRequiredString(args, "propertyId");
|
|
3695
|
+
const description = asOptionalString(args.description);
|
|
3696
|
+
const fallback = args.fallback_value;
|
|
3697
|
+
const result = await callRestApi(
|
|
3698
|
+
"PATCH",
|
|
3699
|
+
`/v1/contact-properties/${encodeURIComponent(propertyId)}`,
|
|
3700
|
+
{
|
|
3701
|
+
...fallback !== void 0 ? { fallback_value: fallback } : {},
|
|
3702
|
+
...description !== void 0 ? { description } : {}
|
|
3703
|
+
},
|
|
3704
|
+
options
|
|
3705
|
+
);
|
|
3706
|
+
return makeToolResult(`Contact property ${result.id} updated.`, result);
|
|
3707
|
+
}
|
|
3708
|
+
if (toolName === "contact_property.delete") {
|
|
3709
|
+
const propertyId = readRequiredString(args, "propertyId");
|
|
3710
|
+
const result = await callRestApi(
|
|
3711
|
+
"DELETE",
|
|
3712
|
+
`/v1/contact-properties/${encodeURIComponent(propertyId)}`,
|
|
3713
|
+
void 0,
|
|
3714
|
+
options
|
|
3715
|
+
);
|
|
3716
|
+
return makeToolResult(`Contact property ${result.id} deleted.`, result);
|
|
3717
|
+
}
|
|
3718
|
+
if (toolName === "contact.get") {
|
|
3719
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3720
|
+
const idOrEmail = readRequiredString(args, "idOrEmail");
|
|
3721
|
+
const result = await callRestApi(
|
|
3722
|
+
"GET",
|
|
3723
|
+
`/v1/contacts/${encodeURIComponent(idOrEmail)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3724
|
+
void 0,
|
|
3725
|
+
options
|
|
3726
|
+
);
|
|
3727
|
+
return makeToolResult(`Contact ${result.email} (${result.status}).`, result);
|
|
3728
|
+
}
|
|
3729
|
+
if (toolName === "contact.delete") {
|
|
3730
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3731
|
+
const idOrEmail = readRequiredString(args, "idOrEmail");
|
|
3732
|
+
const result = await callRestApi(
|
|
3733
|
+
"DELETE",
|
|
3734
|
+
`/v1/contacts/${encodeURIComponent(idOrEmail)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3735
|
+
void 0,
|
|
3736
|
+
options
|
|
3737
|
+
);
|
|
3738
|
+
return makeToolResult(`Contact ${result.id} deleted.`, result);
|
|
3739
|
+
}
|
|
3740
|
+
if (toolName === "contact.get_properties") {
|
|
3741
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3742
|
+
const contactId = readRequiredString(args, "contactId");
|
|
3743
|
+
const result = await callTrpc(
|
|
3744
|
+
"contact.getPropertyValues",
|
|
3745
|
+
{ publicationId, contactId },
|
|
3746
|
+
options,
|
|
3747
|
+
"query"
|
|
3748
|
+
);
|
|
3749
|
+
return makeToolResult(`Property values for contact ${contactId}.`, result);
|
|
3750
|
+
}
|
|
3751
|
+
if (toolName === "contact.set_properties") {
|
|
3752
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3753
|
+
const contactId = readRequiredString(args, "contactId");
|
|
3754
|
+
const values = args.values;
|
|
3755
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
3756
|
+
throw new Error("'values' must be a non-empty array of { propertyId, value }.");
|
|
3757
|
+
}
|
|
3758
|
+
const result = await callTrpc(
|
|
3759
|
+
"contact.setPropertyValues",
|
|
3760
|
+
{ publicationId, contactId, values },
|
|
3761
|
+
options,
|
|
3762
|
+
"mutation"
|
|
3763
|
+
);
|
|
3764
|
+
return makeToolResult(
|
|
3765
|
+
`Set ${values.length} property value(s) for contact ${contactId}.`,
|
|
3766
|
+
result
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3769
|
+
if (toolName === "tag.create") {
|
|
3770
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3771
|
+
const name = readRequiredString(args, "name");
|
|
3772
|
+
const defaultSubscription = asOptionalString(args.default_subscription);
|
|
3773
|
+
if (defaultSubscription !== "opt_in" && defaultSubscription !== "opt_out") {
|
|
3774
|
+
throw new Error("'default_subscription' must be 'opt_in' or 'opt_out'.");
|
|
3775
|
+
}
|
|
3776
|
+
const description = asOptionalString(args.description);
|
|
3777
|
+
const visibility = asOptionalString(args.visibility);
|
|
3778
|
+
const result = await callRestApi(
|
|
3779
|
+
"POST",
|
|
3780
|
+
"/v1/tags",
|
|
3781
|
+
{
|
|
3782
|
+
publication_id: publicationId,
|
|
3783
|
+
name,
|
|
3784
|
+
default_subscription: defaultSubscription,
|
|
3785
|
+
...description !== void 0 ? { description } : {},
|
|
3786
|
+
...visibility ? { visibility } : {}
|
|
3787
|
+
},
|
|
3788
|
+
options
|
|
3789
|
+
);
|
|
3790
|
+
return makeToolResult(`Tag ${result.name} created (${result.id}).`, result);
|
|
3791
|
+
}
|
|
3792
|
+
if (toolName === "tag.list") {
|
|
3793
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3794
|
+
const limit = readOptionalNumber(args, "limit");
|
|
3795
|
+
const params = new URLSearchParams({ publication_id: publicationId });
|
|
3796
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
3797
|
+
const result = await callRestApi(
|
|
3798
|
+
"GET",
|
|
3799
|
+
`/v1/tags?${params.toString()}`,
|
|
3800
|
+
void 0,
|
|
3801
|
+
options
|
|
3802
|
+
);
|
|
3803
|
+
return makeToolResult(`${result.data.length} tag(s).`, result);
|
|
3804
|
+
}
|
|
3805
|
+
if (toolName === "tag.update") {
|
|
3806
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3807
|
+
const tagId = readRequiredString(args, "tagId");
|
|
3808
|
+
const name = asOptionalString(args.name);
|
|
3809
|
+
const description = asOptionalString(args.description);
|
|
3810
|
+
const defaultSubscription = asOptionalString(args.default_subscription);
|
|
3811
|
+
const visibility = asOptionalString(args.visibility);
|
|
3812
|
+
const result = await callRestApi(
|
|
3813
|
+
"PATCH",
|
|
3814
|
+
`/v1/tags/${encodeURIComponent(tagId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3815
|
+
{
|
|
3816
|
+
...name ? { name } : {},
|
|
3817
|
+
...description !== void 0 ? { description } : {},
|
|
3818
|
+
...defaultSubscription ? { default_subscription: defaultSubscription } : {},
|
|
3819
|
+
...visibility ? { visibility } : {}
|
|
3820
|
+
},
|
|
3821
|
+
options
|
|
3822
|
+
);
|
|
3823
|
+
return makeToolResult(`Tag ${result.id} updated.`, result);
|
|
3824
|
+
}
|
|
3825
|
+
if (toolName === "tag.delete") {
|
|
3826
|
+
const publicationId = readRequiredString(args, "publicationId");
|
|
3827
|
+
const tagId = readRequiredString(args, "tagId");
|
|
3828
|
+
const result = await callRestApi(
|
|
3829
|
+
"DELETE",
|
|
3830
|
+
`/v1/tags/${encodeURIComponent(tagId)}?publication_id=${encodeURIComponent(publicationId)}`,
|
|
3831
|
+
void 0,
|
|
3832
|
+
options
|
|
3833
|
+
);
|
|
3834
|
+
return makeToolResult(`Tag ${result.id} deleted.`, result);
|
|
3835
|
+
}
|
|
3836
|
+
if (toolName === "api_key.create") {
|
|
3837
|
+
const name = readRequiredString(args, "name");
|
|
3838
|
+
const permission = asOptionalString(args.permission);
|
|
3839
|
+
const domainId = asOptionalString(args.domain_id);
|
|
3840
|
+
const result = await callRestApi(
|
|
3841
|
+
"POST",
|
|
3842
|
+
"/v1/api-keys",
|
|
3843
|
+
{
|
|
3844
|
+
name,
|
|
3845
|
+
...permission ? { permission } : {},
|
|
3846
|
+
...domainId ? { domain_id: domainId } : {}
|
|
3847
|
+
},
|
|
3848
|
+
options
|
|
3849
|
+
);
|
|
3850
|
+
return makeToolResult(
|
|
3851
|
+
`API key ${result.id} created. Store this token now \u2014 it won't be shown again: ${result.token}`,
|
|
3852
|
+
result
|
|
3853
|
+
);
|
|
3854
|
+
}
|
|
3855
|
+
if (toolName === "api_key.list") {
|
|
3856
|
+
const result = await callRestApi(
|
|
3857
|
+
"GET",
|
|
3858
|
+
"/v1/api-keys",
|
|
3859
|
+
void 0,
|
|
3860
|
+
options
|
|
3861
|
+
);
|
|
3862
|
+
return makeToolResult(`${result.data.length} API key(s).`, result);
|
|
3863
|
+
}
|
|
3864
|
+
if (toolName === "api_key.revoke") {
|
|
3865
|
+
const keyId = readRequiredString(args, "keyId");
|
|
3866
|
+
await callRestApi(
|
|
3867
|
+
"DELETE",
|
|
3868
|
+
`/v1/api-keys/${encodeURIComponent(keyId)}`,
|
|
3869
|
+
void 0,
|
|
3870
|
+
options
|
|
3871
|
+
);
|
|
3872
|
+
return makeToolResult(`API key ${keyId} revoked.`, { id: keyId, revoked: true });
|
|
3873
|
+
}
|
|
3874
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
3875
|
+
}
|
|
3876
|
+
async function readResource(uri, options) {
|
|
3877
|
+
if (uri === "publication://current/brand-guidelines") {
|
|
3878
|
+
return {
|
|
3879
|
+
contents: [
|
|
3880
|
+
{
|
|
3881
|
+
uri,
|
|
3882
|
+
mimeType: "application/json",
|
|
3883
|
+
text: JSON.stringify(
|
|
3884
|
+
{
|
|
3885
|
+
tone: ["clear", "friendly", "direct"],
|
|
3886
|
+
style: "short paragraphs, concrete statements, one CTA",
|
|
3887
|
+
banned: ["vague hype", "unverifiable claims"]
|
|
3888
|
+
},
|
|
3889
|
+
null,
|
|
3890
|
+
2
|
|
3891
|
+
)
|
|
3892
|
+
}
|
|
3893
|
+
]
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
if (uri === "mailtea://capabilities") {
|
|
3897
|
+
return {
|
|
3898
|
+
contents: [
|
|
3899
|
+
{
|
|
3900
|
+
uri,
|
|
3901
|
+
mimeType: "application/json",
|
|
3902
|
+
text: JSON.stringify(
|
|
3903
|
+
{
|
|
3904
|
+
apiBaseUrl: resolveApiBaseUrl(options),
|
|
3905
|
+
hasToken: Boolean(resolveToken(options)),
|
|
3906
|
+
tools: MCP_TOOLS.map((tool) => tool.name),
|
|
3907
|
+
notes: [
|
|
3908
|
+
"All tools call Mailtea API endpoints.",
|
|
3909
|
+
"Use a PAT or Better Auth session token in Authorization header."
|
|
3910
|
+
]
|
|
3911
|
+
},
|
|
3912
|
+
null,
|
|
3913
|
+
2
|
|
3914
|
+
)
|
|
3915
|
+
}
|
|
3916
|
+
]
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
const analyticsUri = parseAnalyticsSummaryUri(uri);
|
|
3920
|
+
if (analyticsUri) {
|
|
3921
|
+
const summary = await loadLatestAnalyticsSummary(options, {
|
|
3922
|
+
publicationId: analyticsUri.publicationId,
|
|
3923
|
+
range: analyticsUri.range
|
|
3924
|
+
});
|
|
3925
|
+
return {
|
|
3926
|
+
contents: [
|
|
3927
|
+
{
|
|
3928
|
+
uri,
|
|
3929
|
+
mimeType: "application/json",
|
|
3930
|
+
text: JSON.stringify(summary, null, 2)
|
|
3931
|
+
}
|
|
3932
|
+
]
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
3936
|
+
}
|
|
3937
|
+
function readPrompt(name) {
|
|
3938
|
+
if (name === "newsletter.draft_from_brief") {
|
|
3939
|
+
return {
|
|
3940
|
+
description: "Create a newsletter draft from a short brief",
|
|
3941
|
+
messages: [
|
|
3942
|
+
{
|
|
3943
|
+
role: "user",
|
|
3944
|
+
content: {
|
|
3945
|
+
type: "text",
|
|
3946
|
+
text: "Write a concise newsletter with clear sections and one CTA."
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
]
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
if (name === "newsletter.subject_line_pack") {
|
|
3953
|
+
return {
|
|
3954
|
+
description: "Generate subject line variants",
|
|
3955
|
+
messages: [
|
|
3956
|
+
{
|
|
3957
|
+
role: "user",
|
|
3958
|
+
content: {
|
|
3959
|
+
type: "text",
|
|
3960
|
+
text: "Generate 10 subject lines with varied tone and urgency."
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
]
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
3967
|
+
}
|
|
3968
|
+
async function handleMcpRequest(request, options = {}) {
|
|
3969
|
+
const id = request.id ?? null;
|
|
3970
|
+
try {
|
|
3971
|
+
switch (request.method) {
|
|
3972
|
+
case "initialize":
|
|
3973
|
+
return response(id, {
|
|
3974
|
+
protocolVersion: "2025-06-18",
|
|
3975
|
+
serverInfo: {
|
|
3976
|
+
name: "mailtea-mcp",
|
|
3977
|
+
version: "0.2.0"
|
|
3978
|
+
},
|
|
3979
|
+
capabilities: {
|
|
3980
|
+
tools: {},
|
|
3981
|
+
resources: {},
|
|
3982
|
+
prompts: {}
|
|
3983
|
+
}
|
|
3984
|
+
});
|
|
3985
|
+
case "tools/list":
|
|
3986
|
+
return response(id, { tools: MCP_TOOLS });
|
|
3987
|
+
case "resources/list":
|
|
3988
|
+
return response(id, { resources: MCP_RESOURCES });
|
|
3989
|
+
case "resources/read": {
|
|
3990
|
+
const uri = asOptionalString(request.params?.uri);
|
|
3991
|
+
if (!uri) {
|
|
3992
|
+
return error(id, -32602, "Missing required param: uri");
|
|
3993
|
+
}
|
|
3994
|
+
return response(id, await readResource(uri, options));
|
|
3995
|
+
}
|
|
3996
|
+
case "prompts/list":
|
|
3997
|
+
return response(id, { prompts: MCP_PROMPTS });
|
|
3998
|
+
case "prompts/get": {
|
|
3999
|
+
const promptName = asOptionalString(request.params?.name);
|
|
4000
|
+
if (!promptName) {
|
|
4001
|
+
return error(id, -32602, "Missing required param: name");
|
|
4002
|
+
}
|
|
4003
|
+
return response(id, readPrompt(promptName));
|
|
4004
|
+
}
|
|
4005
|
+
case "tools/call": {
|
|
4006
|
+
const params = asObject(request.params);
|
|
4007
|
+
const toolName = asOptionalString(params.name);
|
|
4008
|
+
if (!toolName) {
|
|
4009
|
+
return error(id, -32602, "Missing required param: name");
|
|
4010
|
+
}
|
|
4011
|
+
const argumentsValue = params.arguments;
|
|
4012
|
+
if (argumentsValue !== void 0 && (!argumentsValue || typeof argumentsValue !== "object" || Array.isArray(argumentsValue))) {
|
|
4013
|
+
return error(id, -32602, "Param arguments must be an object");
|
|
4014
|
+
}
|
|
4015
|
+
const args = argumentsValue ?? {};
|
|
4016
|
+
const result = await runTool(toolName, args, options);
|
|
4017
|
+
return response(id, result);
|
|
4018
|
+
}
|
|
4019
|
+
case "notifications/initialized":
|
|
4020
|
+
return response(id, {});
|
|
4021
|
+
default:
|
|
4022
|
+
return error(id, -32601, "Method not found", { method: request.method });
|
|
4023
|
+
}
|
|
4024
|
+
} catch (err) {
|
|
4025
|
+
return error(id, -32e3, toErrorMessage(err));
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
export {
|
|
4030
|
+
MCP_TOOLS,
|
|
4031
|
+
MCP_RESOURCES,
|
|
4032
|
+
MCP_PROMPTS,
|
|
4033
|
+
handleMcpRequest
|
|
4034
|
+
};
|