@buzzposter/mcp 0.1.14 → 0.1.15
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/dist/index.js +259 -2825
- package/dist/tools.d.ts +1 -89
- package/dist/tools.js +1383 -3949
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65,9 +65,6 @@ var BuzzPosterClient = class {
|
|
|
65
65
|
async deletePost(id) {
|
|
66
66
|
return this.request("DELETE", `/api/v1/posts/${id}`);
|
|
67
67
|
}
|
|
68
|
-
async retryPost(id) {
|
|
69
|
-
return this.request("POST", `/api/v1/posts/${id}/retry`);
|
|
70
|
-
}
|
|
71
68
|
// Analytics
|
|
72
69
|
async getAnalytics(params) {
|
|
73
70
|
return this.request("GET", "/api/v1/analytics", void 0, params);
|
|
@@ -104,9 +101,6 @@ var BuzzPosterClient = class {
|
|
|
104
101
|
async uploadFromUrl(data) {
|
|
105
102
|
return this.request("POST", "/api/v1/media/upload-from-url", data);
|
|
106
103
|
}
|
|
107
|
-
async getUploadUrl(data) {
|
|
108
|
-
return this.request("POST", "/api/v1/media/presign", data);
|
|
109
|
-
}
|
|
110
104
|
async listMedia() {
|
|
111
105
|
return this.request("GET", "/api/v1/media");
|
|
112
106
|
}
|
|
@@ -145,9 +139,6 @@ var BuzzPosterClient = class {
|
|
|
145
139
|
async listTags() {
|
|
146
140
|
return this.request("GET", "/api/v1/newsletters/tags");
|
|
147
141
|
}
|
|
148
|
-
async listSequences() {
|
|
149
|
-
return this.request("GET", "/api/v1/newsletters/sequences");
|
|
150
|
-
}
|
|
151
142
|
async listForms() {
|
|
152
143
|
return this.request("GET", "/api/v1/newsletters/forms");
|
|
153
144
|
}
|
|
@@ -158,36 +149,15 @@ var BuzzPosterClient = class {
|
|
|
158
149
|
async listAutomations(params) {
|
|
159
150
|
return this.request("GET", "/api/v1/newsletters/automations", void 0, params);
|
|
160
151
|
}
|
|
161
|
-
async enrollInAutomation(automationId, data) {
|
|
162
|
-
return this.request("POST", `/api/v1/newsletters/automations/${automationId}/enroll`, data);
|
|
163
|
-
}
|
|
164
152
|
async listSegments(params) {
|
|
165
153
|
return this.request("GET", "/api/v1/newsletters/segments", void 0, params);
|
|
166
154
|
}
|
|
167
155
|
async getSegment(id, params) {
|
|
168
156
|
return this.request("GET", `/api/v1/newsletters/segments/${id}`, void 0, params);
|
|
169
157
|
}
|
|
170
|
-
async getSegmentMembers(segmentId, params) {
|
|
171
|
-
return this.request("GET", `/api/v1/newsletters/segments/${segmentId}/members`, void 0, params);
|
|
172
|
-
}
|
|
173
|
-
async deleteSegment(id) {
|
|
174
|
-
return this.request("DELETE", `/api/v1/newsletters/segments/${id}`);
|
|
175
|
-
}
|
|
176
|
-
async recalculateSegment(id) {
|
|
177
|
-
return this.request("POST", `/api/v1/newsletters/segments/${id}/recalculate`);
|
|
178
|
-
}
|
|
179
158
|
async listCustomFields() {
|
|
180
159
|
return this.request("GET", "/api/v1/newsletters/custom-fields");
|
|
181
160
|
}
|
|
182
|
-
async createCustomField(data) {
|
|
183
|
-
return this.request("POST", "/api/v1/newsletters/custom-fields", data);
|
|
184
|
-
}
|
|
185
|
-
async updateCustomField(id, data) {
|
|
186
|
-
return this.request("PUT", `/api/v1/newsletters/custom-fields/${id}`, data);
|
|
187
|
-
}
|
|
188
|
-
async deleteCustomField(id) {
|
|
189
|
-
return this.request("DELETE", `/api/v1/newsletters/custom-fields/${id}`);
|
|
190
|
-
}
|
|
191
161
|
async tagSubscriber(subscriberId, data) {
|
|
192
162
|
return this.request("POST", `/api/v1/newsletters/subscribers/${subscriberId}/tags`, data);
|
|
193
163
|
}
|
|
@@ -197,33 +167,12 @@ var BuzzPosterClient = class {
|
|
|
197
167
|
async deleteSubscriber(id) {
|
|
198
168
|
return this.request("DELETE", `/api/v1/newsletters/subscribers/${id}`);
|
|
199
169
|
}
|
|
200
|
-
async bulkCreateSubscribers(data) {
|
|
201
|
-
return this.request("POST", "/api/v1/newsletters/subscribers/bulk", data);
|
|
202
|
-
}
|
|
203
|
-
async getReferralProgram() {
|
|
204
|
-
return this.request("GET", "/api/v1/newsletters/referral-program");
|
|
205
|
-
}
|
|
206
|
-
async listEspTiers() {
|
|
207
|
-
return this.request("GET", "/api/v1/newsletters/tiers");
|
|
208
|
-
}
|
|
209
170
|
async listPostTemplates() {
|
|
210
171
|
return this.request("GET", "/api/v1/newsletters/post-templates");
|
|
211
172
|
}
|
|
212
173
|
async getPostAggregateStats() {
|
|
213
174
|
return this.request("GET", "/api/v1/newsletters/broadcasts/aggregate-stats");
|
|
214
175
|
}
|
|
215
|
-
async deleteBroadcast(id) {
|
|
216
|
-
return this.request("DELETE", `/api/v1/newsletters/broadcasts/${id}`);
|
|
217
|
-
}
|
|
218
|
-
async listEspWebhooks() {
|
|
219
|
-
return this.request("GET", "/api/v1/newsletters/webhooks");
|
|
220
|
-
}
|
|
221
|
-
async createEspWebhook(data) {
|
|
222
|
-
return this.request("POST", "/api/v1/newsletters/webhooks", data);
|
|
223
|
-
}
|
|
224
|
-
async deleteEspWebhook(id) {
|
|
225
|
-
return this.request("DELETE", `/api/v1/newsletters/webhooks/${id}`);
|
|
226
|
-
}
|
|
227
176
|
// Knowledge
|
|
228
177
|
async listKnowledge(params) {
|
|
229
178
|
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
@@ -251,12 +200,6 @@ var BuzzPosterClient = class {
|
|
|
251
200
|
async updateAudience(id, data) {
|
|
252
201
|
return this.request("PUT", `/api/v1/audiences/${id}`, data);
|
|
253
202
|
}
|
|
254
|
-
async listAudiences() {
|
|
255
|
-
return this.request("GET", "/api/v1/audiences");
|
|
256
|
-
}
|
|
257
|
-
async deleteAudience(id) {
|
|
258
|
-
return this.request("DELETE", `/api/v1/audiences/${id}`);
|
|
259
|
-
}
|
|
260
203
|
// Newsletter Templates
|
|
261
204
|
async getTemplate(id) {
|
|
262
205
|
if (id) return this.request("GET", `/api/v1/templates/${id}`);
|
|
@@ -348,10 +291,6 @@ var BuzzPosterClient = class {
|
|
|
348
291
|
async getPublishingRules() {
|
|
349
292
|
return this.request("GET", "/api/v1/publishing-rules");
|
|
350
293
|
}
|
|
351
|
-
// Audit Log
|
|
352
|
-
async getAuditLog(params) {
|
|
353
|
-
return this.request("GET", "/api/v1/audit-log", void 0, params);
|
|
354
|
-
}
|
|
355
294
|
// Newsletter Validation
|
|
356
295
|
async validateNewsletter(data) {
|
|
357
296
|
return this.request("POST", "/api/v1/newsletters/validate", data);
|
|
@@ -364,108 +303,6 @@ var BuzzPosterClient = class {
|
|
|
364
303
|
async testBroadcast(id, data) {
|
|
365
304
|
return this.request("POST", `/api/v1/newsletters/broadcasts/${id}/test`, data);
|
|
366
305
|
}
|
|
367
|
-
// Carousel templates
|
|
368
|
-
async getCarouselTemplate() {
|
|
369
|
-
return this.request("GET", "/api/v1/carousel-templates");
|
|
370
|
-
}
|
|
371
|
-
async updateCarouselTemplate(data) {
|
|
372
|
-
return this.request("PUT", "/api/v1/carousel-templates", data);
|
|
373
|
-
}
|
|
374
|
-
async deleteCarouselTemplate() {
|
|
375
|
-
return this.request("DELETE", "/api/v1/carousel-templates");
|
|
376
|
-
}
|
|
377
|
-
async generateCarousel(data) {
|
|
378
|
-
return this.request("POST", "/api/v1/carousel-templates/generate", data);
|
|
379
|
-
}
|
|
380
|
-
async previewCarousel(data) {
|
|
381
|
-
return this.request("POST", "/api/v1/carousel-templates/preview", data);
|
|
382
|
-
}
|
|
383
|
-
// Canva
|
|
384
|
-
async getCanvaStatus() {
|
|
385
|
-
return this.request("GET", "/api/v1/canva/status");
|
|
386
|
-
}
|
|
387
|
-
async canvaUploadAsset(data) {
|
|
388
|
-
return this.request("POST", "/api/v1/canva/upload-asset", data);
|
|
389
|
-
}
|
|
390
|
-
async canvaCreateDesign(data) {
|
|
391
|
-
return this.request("POST", "/api/v1/canva/designs", data);
|
|
392
|
-
}
|
|
393
|
-
async canvaExportDesign(data) {
|
|
394
|
-
return this.request("POST", "/api/v1/canva/exports", data);
|
|
395
|
-
}
|
|
396
|
-
async canvaSearchDesigns(query) {
|
|
397
|
-
return this.request("GET", "/api/v1/canva/designs", void 0, { query });
|
|
398
|
-
}
|
|
399
|
-
async canvaGetDesign(designId) {
|
|
400
|
-
return this.request("GET", `/api/v1/canva/designs/${designId}`);
|
|
401
|
-
}
|
|
402
|
-
// SendGrid Newsletter (BuzzPoster-managed)
|
|
403
|
-
async sgListLists() {
|
|
404
|
-
return this.request("GET", "/api/v1/newsletter/lists");
|
|
405
|
-
}
|
|
406
|
-
async sgCreateList(data) {
|
|
407
|
-
return this.request("POST", "/api/v1/newsletter/lists", data);
|
|
408
|
-
}
|
|
409
|
-
async sgUpdateList(id, data) {
|
|
410
|
-
return this.request("PUT", `/api/v1/newsletter/lists/${id}`, data);
|
|
411
|
-
}
|
|
412
|
-
async sgDeleteList(id) {
|
|
413
|
-
return this.request("DELETE", `/api/v1/newsletter/lists/${id}`);
|
|
414
|
-
}
|
|
415
|
-
async sgListSubscribers(params) {
|
|
416
|
-
return this.request("GET", "/api/v1/newsletter/subscribers", void 0, params);
|
|
417
|
-
}
|
|
418
|
-
async sgAddSubscriber(data) {
|
|
419
|
-
return this.request("POST", "/api/v1/newsletter/subscribers", data);
|
|
420
|
-
}
|
|
421
|
-
async sgGetSubscriber(id) {
|
|
422
|
-
return this.request("GET", `/api/v1/newsletter/subscribers/${id}`);
|
|
423
|
-
}
|
|
424
|
-
async sgUpdateSubscriber(id, data) {
|
|
425
|
-
return this.request("PUT", `/api/v1/newsletter/subscribers/${id}`, data);
|
|
426
|
-
}
|
|
427
|
-
async sgRemoveSubscriber(id) {
|
|
428
|
-
return this.request("DELETE", `/api/v1/newsletter/subscribers/${id}`);
|
|
429
|
-
}
|
|
430
|
-
async sgBulkImportSubscribers(data) {
|
|
431
|
-
return this.request("POST", "/api/v1/newsletter/subscribers/bulk", data);
|
|
432
|
-
}
|
|
433
|
-
async sgListSends(params) {
|
|
434
|
-
return this.request("GET", "/api/v1/newsletter/sends", void 0, params);
|
|
435
|
-
}
|
|
436
|
-
async sgGetSend(id) {
|
|
437
|
-
return this.request("GET", `/api/v1/newsletter/sends/${id}`);
|
|
438
|
-
}
|
|
439
|
-
async sgCreateSend(data) {
|
|
440
|
-
return this.request("POST", "/api/v1/newsletter/sends", data);
|
|
441
|
-
}
|
|
442
|
-
async sgUpdateSend(id, data) {
|
|
443
|
-
return this.request("PUT", `/api/v1/newsletter/sends/${id}`, data);
|
|
444
|
-
}
|
|
445
|
-
async sgDeleteSend(id) {
|
|
446
|
-
return this.request("DELETE", `/api/v1/newsletter/sends/${id}`);
|
|
447
|
-
}
|
|
448
|
-
async sgSendNewsletter(id) {
|
|
449
|
-
return this.request("POST", `/api/v1/newsletter/sends/${id}/send`);
|
|
450
|
-
}
|
|
451
|
-
async sgTestSend(id, data) {
|
|
452
|
-
return this.request("POST", `/api/v1/newsletter/sends/${id}/test`, data);
|
|
453
|
-
}
|
|
454
|
-
async sgScheduleSend(id, data) {
|
|
455
|
-
return this.request("POST", `/api/v1/newsletter/sends/${id}/schedule`, data);
|
|
456
|
-
}
|
|
457
|
-
async sgCancelSend(id) {
|
|
458
|
-
return this.request("POST", `/api/v1/newsletter/sends/${id}/cancel`);
|
|
459
|
-
}
|
|
460
|
-
async sgAuthenticateDomain(domain) {
|
|
461
|
-
return this.request("POST", "/api/v1/newsletter/domains/authenticate", { domain });
|
|
462
|
-
}
|
|
463
|
-
async sgValidateDomain(domainId) {
|
|
464
|
-
return this.request("POST", `/api/v1/newsletter/domains/${domainId}/validate`);
|
|
465
|
-
}
|
|
466
|
-
async sgListDomains() {
|
|
467
|
-
return this.request("GET", "/api/v1/newsletter/domains");
|
|
468
|
-
}
|
|
469
306
|
};
|
|
470
307
|
|
|
471
308
|
// src/tools/posts.ts
|
|
@@ -473,23 +310,7 @@ import { z } from "zod";
|
|
|
473
310
|
function registerPostTools(server2, client2) {
|
|
474
311
|
server2.tool(
|
|
475
312
|
"post",
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Always show the user a preview of the content before calling this tool. Call get_brand_voice and get_audience before composing if not already loaded this session. Never call with confirmed=true without explicit user approval.
|
|
479
|
-
|
|
480
|
-
REQUIRED WORKFLOW:
|
|
481
|
-
1. BEFORE creating any content, call get_brand_voice and get_audience to match the customer's tone
|
|
482
|
-
2. BEFORE publishing, call get_publishing_rules to check safety settings
|
|
483
|
-
3. ALWAYS set confirmed=false first to get a preview -- never set confirmed=true on the first call
|
|
484
|
-
4. Show the user a visual preview of the post before confirming
|
|
485
|
-
5. Only set confirmed=true after the user explicitly says to publish/post/send
|
|
486
|
-
6. If publishing_rules.social_default_action is 'draft', create as draft unless the user explicitly asks to publish
|
|
487
|
-
7. If publishing_rules.require_double_confirm_social is true, ask for confirmation twice before publishing
|
|
488
|
-
8. NEVER publish immediately without user confirmation, even if the user says "post this" -- always show the preview first
|
|
489
|
-
|
|
490
|
-
When confirmed=false, this tool returns a structured preview with content, platform details, character counts, safety checks, and a confirmation message. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.
|
|
491
|
-
|
|
492
|
-
IMPORTANT: Always show the user a preview of the post content before publishing. Never publish without explicit user approval. If no preview has been shown yet, do NOT set confirmed=true -- render the preview first.`,
|
|
313
|
+
"Publish a post to one or more social platforms. Set confirmed=false to preview first.",
|
|
493
314
|
{
|
|
494
315
|
content: z.string().optional().describe("The text content of the post"),
|
|
495
316
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -581,118 +402,9 @@ Call this tool again with confirmed=true to proceed.`;
|
|
|
581
402
|
};
|
|
582
403
|
}
|
|
583
404
|
);
|
|
584
|
-
server2.tool(
|
|
585
|
-
"cross_post",
|
|
586
|
-
`Post the same content to all connected platforms at once.
|
|
587
|
-
|
|
588
|
-
Always show the user a preview of the content before calling this tool. Call get_brand_voice and get_audience before composing if not already loaded this session. Never call with confirmed=true without explicit user approval.
|
|
589
|
-
|
|
590
|
-
REQUIRED WORKFLOW:
|
|
591
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
592
|
-
2. BEFORE publishing, call get_publishing_rules
|
|
593
|
-
3. ALWAYS set confirmed=false first to preview
|
|
594
|
-
4. Show the user a visual preview showing how the post will appear on each platform
|
|
595
|
-
5. This is a high-impact action (goes to ALL platforms) -- always require explicit confirmation
|
|
596
|
-
6. If publishing_rules.require_double_confirm_social is true, ask twice
|
|
597
|
-
|
|
598
|
-
When confirmed=false, this tool returns a structured preview with content, platform details, character counts, safety checks, and a confirmation message. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.`,
|
|
599
|
-
{
|
|
600
|
-
content: z.string().describe("The text content to post everywhere"),
|
|
601
|
-
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
602
|
-
confirmed: z.boolean().default(false).describe(
|
|
603
|
-
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
604
|
-
)
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
title: "Cross-Post to All Platforms",
|
|
608
|
-
readOnlyHint: false,
|
|
609
|
-
destructiveHint: false,
|
|
610
|
-
idempotentHint: false,
|
|
611
|
-
openWorldHint: true
|
|
612
|
-
},
|
|
613
|
-
async (args) => {
|
|
614
|
-
const accountsData = await client2.listAccounts();
|
|
615
|
-
const platforms = (accountsData.accounts ?? []).map(
|
|
616
|
-
(a) => a.platform
|
|
617
|
-
);
|
|
618
|
-
if (platforms.length === 0) {
|
|
619
|
-
return {
|
|
620
|
-
content: [
|
|
621
|
-
{
|
|
622
|
-
type: "text",
|
|
623
|
-
text: "No connected social accounts found. Connect accounts first."
|
|
624
|
-
}
|
|
625
|
-
]
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
if (args.confirmed !== true) {
|
|
629
|
-
let rulesText = "";
|
|
630
|
-
try {
|
|
631
|
-
const rules = await client2.getPublishingRules();
|
|
632
|
-
const blockedWords = rules.blockedWords ?? [];
|
|
633
|
-
const foundBlocked = [];
|
|
634
|
-
if (args.content && blockedWords.length > 0) {
|
|
635
|
-
const lower = args.content.toLowerCase();
|
|
636
|
-
for (const word of blockedWords) {
|
|
637
|
-
if (lower.includes(word.toLowerCase())) foundBlocked.push(word);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
rulesText = `
|
|
641
|
-
### Safety Checks
|
|
642
|
-
`;
|
|
643
|
-
rulesText += `- Blocked words: ${foundBlocked.length > 0 ? `**FAILED** (found: ${foundBlocked.join(", ")})` : "PASS"}
|
|
644
|
-
`;
|
|
645
|
-
rulesText += `- Default action: ${rules.socialDefaultAction ?? "draft"}
|
|
646
|
-
`;
|
|
647
|
-
rulesText += `- Double confirmation required: ${rules.requireDoubleConfirmSocial ? "yes" : "no"}
|
|
648
|
-
`;
|
|
649
|
-
} catch {
|
|
650
|
-
}
|
|
651
|
-
const preview = `## Cross-Post Preview
|
|
652
|
-
|
|
653
|
-
**Content:** "${args.content}"
|
|
654
|
-
**Platforms:** ${platforms.join(", ")} (${platforms.length} platforms)
|
|
655
|
-
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
656
|
-
` : "") + rulesText + `
|
|
657
|
-
**Action:** This will be published **immediately** to **all ${platforms.length} connected platforms**. This is a high-impact action.
|
|
658
|
-
|
|
659
|
-
Call this tool again with confirmed=true to proceed.`;
|
|
660
|
-
return { content: [{ type: "text", text: preview }] };
|
|
661
|
-
}
|
|
662
|
-
const body = {
|
|
663
|
-
content: args.content,
|
|
664
|
-
platforms: platforms.map((p) => ({ platform: p })),
|
|
665
|
-
publishNow: true
|
|
666
|
-
};
|
|
667
|
-
if (args.media_urls?.length) {
|
|
668
|
-
body.mediaItems = args.media_urls.map((url) => ({
|
|
669
|
-
type: url.match(/\.(mp4|mov|avi|webm)$/i) ? "video" : "image",
|
|
670
|
-
url
|
|
671
|
-
}));
|
|
672
|
-
}
|
|
673
|
-
const result = await client2.createPost(body);
|
|
674
|
-
return {
|
|
675
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
);
|
|
679
405
|
server2.tool(
|
|
680
406
|
"schedule_post",
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
Always show the user a preview of the content before calling this tool. Call get_brand_voice and get_audience before composing if not already loaded this session. Never call with confirmed=true without explicit user approval.
|
|
684
|
-
|
|
685
|
-
REQUIRED WORKFLOW:
|
|
686
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
687
|
-
2. BEFORE scheduling, call get_publishing_rules
|
|
688
|
-
3. ALWAYS set confirmed=false first to preview
|
|
689
|
-
4. Show the user a visual preview with the scheduled date/time before confirming
|
|
690
|
-
5. Only confirm after explicit user approval
|
|
691
|
-
6. If the user hasn't specified a time, suggest one based on get_queue time slots
|
|
692
|
-
|
|
693
|
-
When confirmed=false, this tool returns a structured preview with content, platform details, character counts, safety checks, and a confirmation message. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.
|
|
694
|
-
|
|
695
|
-
IMPORTANT: Always show the user a preview of the post content before scheduling. Never schedule without explicit user approval. If no preview has been shown yet, do NOT set confirmed=true -- render the preview first.`,
|
|
407
|
+
"Schedule a post for future publication. Set confirmed=false to preview first.",
|
|
696
408
|
{
|
|
697
409
|
content: z.string().optional().describe("The text content of the post"),
|
|
698
410
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -768,7 +480,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
768
480
|
);
|
|
769
481
|
server2.tool(
|
|
770
482
|
"create_draft",
|
|
771
|
-
"Save a post as a draft without publishing.
|
|
483
|
+
"Save a post as a draft without publishing. No confirmation needed.",
|
|
772
484
|
{
|
|
773
485
|
content: z.string().optional().describe("The text content of the draft"),
|
|
774
486
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
@@ -811,7 +523,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
811
523
|
);
|
|
812
524
|
server2.tool(
|
|
813
525
|
"list_posts",
|
|
814
|
-
"List recent posts with
|
|
526
|
+
"List recent posts with status and platform details.",
|
|
815
527
|
{
|
|
816
528
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
817
529
|
limit: z.string().optional().describe("Number of posts to return")
|
|
@@ -835,7 +547,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
835
547
|
);
|
|
836
548
|
server2.tool(
|
|
837
549
|
"get_post",
|
|
838
|
-
"Get detailed information about a specific post.",
|
|
550
|
+
"Get detailed information about a specific post by ID.",
|
|
839
551
|
{
|
|
840
552
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
841
553
|
},
|
|
@@ -853,117 +565,13 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
853
565
|
};
|
|
854
566
|
}
|
|
855
567
|
);
|
|
856
|
-
server2.tool(
|
|
857
|
-
"retry_post",
|
|
858
|
-
"Retry a post that failed to publish on one or more platforms. Only retries the failed platforms -- already-published platforms are not affected. Use this when a post shows 'failed' or 'partial' status.",
|
|
859
|
-
{
|
|
860
|
-
post_id: z.string().describe("The ID of the post to retry"),
|
|
861
|
-
confirmed: z.boolean().default(false).describe(
|
|
862
|
-
"Set to true to confirm retry. If false/missing, shows post details first."
|
|
863
|
-
)
|
|
864
|
-
},
|
|
865
|
-
{
|
|
866
|
-
title: "Retry Failed Post",
|
|
867
|
-
readOnlyHint: false,
|
|
868
|
-
destructiveHint: false,
|
|
869
|
-
idempotentHint: true,
|
|
870
|
-
openWorldHint: true
|
|
871
|
-
},
|
|
872
|
-
async (args) => {
|
|
873
|
-
if (args.confirmed !== true) {
|
|
874
|
-
const post = await client2.getPost(args.post_id);
|
|
875
|
-
const platforms = post.platforms ?? [];
|
|
876
|
-
const failed = platforms.filter((p) => p.status === "failed");
|
|
877
|
-
const published = platforms.filter((p) => p.status === "published");
|
|
878
|
-
let text = `## Retry Post Preview
|
|
879
|
-
|
|
880
|
-
`;
|
|
881
|
-
text += `**Post ID:** ${args.post_id}
|
|
882
|
-
`;
|
|
883
|
-
text += `**Content:** "${(post.content ?? "").substring(0, 100)}"
|
|
884
|
-
`;
|
|
885
|
-
text += `**Status:** ${post.status}
|
|
886
|
-
|
|
887
|
-
`;
|
|
888
|
-
if (published.length > 0) {
|
|
889
|
-
text += `**Already published on:** ${published.map((p) => p.platform).join(", ")}
|
|
890
|
-
`;
|
|
891
|
-
}
|
|
892
|
-
if (failed.length > 0) {
|
|
893
|
-
text += `**Failed on:** ${failed.map((p) => `${p.platform} (${p.error ?? "unknown error"})`).join(", ")}
|
|
894
|
-
`;
|
|
895
|
-
}
|
|
896
|
-
text += `
|
|
897
|
-
Retrying will only attempt the failed platforms. Call this tool again with confirmed=true to proceed.`;
|
|
898
|
-
return { content: [{ type: "text", text }] };
|
|
899
|
-
}
|
|
900
|
-
const result = await client2.retryPost(args.post_id);
|
|
901
|
-
return {
|
|
902
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
);
|
|
906
568
|
}
|
|
907
569
|
|
|
908
570
|
// src/tools/accounts.ts
|
|
909
571
|
function registerAccountTools(server2, client2) {
|
|
910
|
-
server2.tool(
|
|
911
|
-
"list_accounts",
|
|
912
|
-
"List all connected accounts \u2014 both social media platforms (Twitter, Instagram, etc.) and email service provider / ESP (Kit, Beehiiv, Mailchimp). Use this whenever the user asks about their accounts, connections, or integrations.",
|
|
913
|
-
{},
|
|
914
|
-
{
|
|
915
|
-
title: "List Connected Accounts",
|
|
916
|
-
readOnlyHint: true,
|
|
917
|
-
destructiveHint: false,
|
|
918
|
-
idempotentHint: true,
|
|
919
|
-
openWorldHint: false
|
|
920
|
-
},
|
|
921
|
-
async () => {
|
|
922
|
-
const result = await client2.listAccounts();
|
|
923
|
-
const accounts = result.accounts ?? [];
|
|
924
|
-
const esp = result.esp;
|
|
925
|
-
if (accounts.length === 0 && !esp) {
|
|
926
|
-
return {
|
|
927
|
-
content: [{
|
|
928
|
-
type: "text",
|
|
929
|
-
text: "## Connected Accounts\n\nNo accounts connected. Connect social media accounts and/or an email service provider via the dashboard."
|
|
930
|
-
}]
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
let text = "## Connected Accounts\n\n";
|
|
934
|
-
text += "### Social Media\n";
|
|
935
|
-
if (accounts.length > 0) {
|
|
936
|
-
for (const a of accounts) {
|
|
937
|
-
const icon = a.isActive ? "\u2705" : "\u274C";
|
|
938
|
-
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
939
|
-
const name = a.username || a.displayName || a.platform;
|
|
940
|
-
text += `${icon} **${platform}** \u2014 @${name}
|
|
941
|
-
`;
|
|
942
|
-
}
|
|
943
|
-
} else {
|
|
944
|
-
text += "No social accounts connected.\n";
|
|
945
|
-
}
|
|
946
|
-
text += "\n### Email Service Provider (ESP)\n";
|
|
947
|
-
if (esp && esp.connected) {
|
|
948
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
949
|
-
text += `\u2705 **${provider}** \u2014 Connected`;
|
|
950
|
-
if (esp.publicationId) text += ` (Publication: ${esp.publicationId})`;
|
|
951
|
-
text += "\n";
|
|
952
|
-
} else if (esp) {
|
|
953
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
954
|
-
text += `\u26A0\uFE0F **${provider}** \u2014 Provider set but API key missing
|
|
955
|
-
`;
|
|
956
|
-
} else {
|
|
957
|
-
text += "No ESP configured.\n";
|
|
958
|
-
}
|
|
959
|
-
return {
|
|
960
|
-
content: [{ type: "text", text }]
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
);
|
|
964
572
|
server2.tool(
|
|
965
573
|
"check_accounts_health",
|
|
966
|
-
"Check
|
|
574
|
+
"Check health status of connected social accounts. Direct user to BuzzPoster web UI to reconnect broken accounts.",
|
|
967
575
|
{},
|
|
968
576
|
{
|
|
969
577
|
title: "Check Account Health",
|
|
@@ -1016,7 +624,7 @@ import { z as z2 } from "zod";
|
|
|
1016
624
|
function registerAnalyticsTools(server2, client2) {
|
|
1017
625
|
server2.tool(
|
|
1018
626
|
"get_analytics",
|
|
1019
|
-
"Get
|
|
627
|
+
"Get social media post performance analytics. Filter by platform and date range.",
|
|
1020
628
|
{
|
|
1021
629
|
platform: z2.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook"),
|
|
1022
630
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
@@ -1047,7 +655,7 @@ import { z as z3 } from "zod";
|
|
|
1047
655
|
function registerInboxTools(server2, client2) {
|
|
1048
656
|
server2.tool(
|
|
1049
657
|
"list_conversations",
|
|
1050
|
-
"List DM conversations across all connected
|
|
658
|
+
"List DM conversations across all connected platforms.",
|
|
1051
659
|
{
|
|
1052
660
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
1053
661
|
},
|
|
@@ -1089,12 +697,7 @@ function registerInboxTools(server2, client2) {
|
|
|
1089
697
|
);
|
|
1090
698
|
server2.tool(
|
|
1091
699
|
"reply_to_conversation",
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
REQUIRED WORKFLOW:
|
|
1095
|
-
1. ALWAYS set confirmed=false first to preview the reply
|
|
1096
|
-
2. Show the user the reply text and which platform/conversation it will be sent to
|
|
1097
|
-
3. Only confirm after explicit user approval`,
|
|
700
|
+
"Send a DM reply. Set confirmed=false to preview first.",
|
|
1098
701
|
{
|
|
1099
702
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
1100
703
|
message: z3.string().describe("The reply message text"),
|
|
@@ -1150,12 +753,7 @@ This will send a DM reply on the external platform. Call this tool again with co
|
|
|
1150
753
|
);
|
|
1151
754
|
server2.tool(
|
|
1152
755
|
"reply_to_comment",
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
REQUIRED WORKFLOW:
|
|
1156
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1157
|
-
2. Show the user the reply and the original comment for context
|
|
1158
|
-
3. Only confirm after explicit user approval -- public replies are visible to everyone`,
|
|
756
|
+
"Reply to a comment publicly. Set confirmed=false to preview first.",
|
|
1159
757
|
{
|
|
1160
758
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
1161
759
|
message: z3.string().describe("The reply text"),
|
|
@@ -1207,12 +805,7 @@ This will post a public reply. Call this tool again with confirmed=true to send.
|
|
|
1207
805
|
);
|
|
1208
806
|
server2.tool(
|
|
1209
807
|
"reply_to_review",
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
REQUIRED WORKFLOW:
|
|
1213
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1214
|
-
2. Show the user the reply, the original review, and the star rating for context
|
|
1215
|
-
3. Only confirm after explicit user approval -- public replies represent the brand`,
|
|
808
|
+
"Reply to a Facebook review publicly. Set confirmed=false to preview first.",
|
|
1216
809
|
{
|
|
1217
810
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
1218
811
|
message: z3.string().describe("The reply text"),
|
|
@@ -1251,7 +844,7 @@ import { z as z4 } from "zod";
|
|
|
1251
844
|
function registerMediaTools(server2, client2) {
|
|
1252
845
|
server2.tool(
|
|
1253
846
|
"upload_from_url",
|
|
1254
|
-
"Upload media from a public URL
|
|
847
|
+
"Upload media from a public URL to storage. Returns a CDN URL for use in posts.",
|
|
1255
848
|
{
|
|
1256
849
|
url: z4.string().url().describe("Public URL of the image or video to upload"),
|
|
1257
850
|
filename: z4.string().optional().describe("Optional filename override (including extension)"),
|
|
@@ -1279,33 +872,9 @@ function registerMediaTools(server2, client2) {
|
|
|
1279
872
|
};
|
|
1280
873
|
}
|
|
1281
874
|
);
|
|
1282
|
-
server2.tool(
|
|
1283
|
-
"get_upload_url",
|
|
1284
|
-
"Get a pre-signed URL for direct client upload to storage. The URL is valid for 5 minutes. After uploading to the URL, the file will be available at the returned CDN URL.",
|
|
1285
|
-
{
|
|
1286
|
-
filename: z4.string().describe("The filename including extension (e.g. photo.jpg)"),
|
|
1287
|
-
content_type: z4.string().describe("MIME type of the file (e.g. image/jpeg, video/mp4)")
|
|
1288
|
-
},
|
|
1289
|
-
{
|
|
1290
|
-
title: "Get Upload URL",
|
|
1291
|
-
readOnlyHint: true,
|
|
1292
|
-
destructiveHint: false,
|
|
1293
|
-
idempotentHint: false,
|
|
1294
|
-
openWorldHint: false
|
|
1295
|
-
},
|
|
1296
|
-
async (args) => {
|
|
1297
|
-
const result = await client2.getUploadUrl({
|
|
1298
|
-
filename: args.filename,
|
|
1299
|
-
content_type: args.content_type
|
|
1300
|
-
});
|
|
1301
|
-
return {
|
|
1302
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
);
|
|
1306
875
|
server2.tool(
|
|
1307
876
|
"list_media",
|
|
1308
|
-
"List all uploaded media files
|
|
877
|
+
"List all uploaded media files.",
|
|
1309
878
|
{},
|
|
1310
879
|
{
|
|
1311
880
|
title: "List Media Library",
|
|
@@ -1328,12 +897,7 @@ function registerMediaTools(server2, client2) {
|
|
|
1328
897
|
);
|
|
1329
898
|
server2.tool(
|
|
1330
899
|
"delete_media",
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
REQUIRED WORKFLOW:
|
|
1334
|
-
1. ALWAYS set confirmed=false first
|
|
1335
|
-
2. Show the user which file will be deleted (filename, URL, size)
|
|
1336
|
-
3. Only confirm after explicit approval`,
|
|
900
|
+
"Delete a media file. Set confirmed=false to preview first.",
|
|
1337
901
|
{
|
|
1338
902
|
key: z4.string().describe("The key/path of the media file to delete"),
|
|
1339
903
|
confirmed: z4.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
@@ -1367,7 +931,7 @@ import { z as z5 } from "zod";
|
|
|
1367
931
|
function registerNewsletterTools(server2, client2, options = {}) {
|
|
1368
932
|
server2.tool(
|
|
1369
933
|
"list_subscribers",
|
|
1370
|
-
"List email subscribers from
|
|
934
|
+
"List email subscribers from the connected ESP.",
|
|
1371
935
|
{
|
|
1372
936
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
1373
937
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
@@ -1391,7 +955,7 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1391
955
|
);
|
|
1392
956
|
server2.tool(
|
|
1393
957
|
"add_subscriber",
|
|
1394
|
-
"Add a new
|
|
958
|
+
"Add a new subscriber to the mailing list.",
|
|
1395
959
|
{
|
|
1396
960
|
email: z5.string().describe("Email address of the subscriber"),
|
|
1397
961
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
@@ -1417,7 +981,7 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1417
981
|
);
|
|
1418
982
|
server2.tool(
|
|
1419
983
|
"create_newsletter",
|
|
1420
|
-
|
|
984
|
+
"Push a newsletter to the user's ESP as a draft. Show an HTML preview to the user before calling this.",
|
|
1421
985
|
{
|
|
1422
986
|
subject: z5.string().describe("Email subject line"),
|
|
1423
987
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
@@ -1447,7 +1011,7 @@ ${args.content}` }] : []
|
|
|
1447
1011
|
);
|
|
1448
1012
|
server2.tool(
|
|
1449
1013
|
"update_newsletter",
|
|
1450
|
-
|
|
1014
|
+
"Update an existing newsletter draft.",
|
|
1451
1015
|
{
|
|
1452
1016
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to update"),
|
|
1453
1017
|
subject: z5.string().optional().describe("Updated subject line"),
|
|
@@ -1479,20 +1043,7 @@ ${args.content}` }] : []
|
|
|
1479
1043
|
if (options.allowDirectSend) {
|
|
1480
1044
|
server2.tool(
|
|
1481
1045
|
"send_newsletter",
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
REQUIRED WORKFLOW:
|
|
1485
|
-
1. ALWAYS call get_publishing_rules first
|
|
1486
|
-
2. NEVER send without showing a full visual preview of the newsletter first
|
|
1487
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt, not a send
|
|
1488
|
-
4. Tell the user exactly how many subscribers will receive this email
|
|
1489
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1490
|
-
- First: "Are you sure you want to send this to [X] subscribers?"
|
|
1491
|
-
- Second: "This is irreversible. Type 'send' to confirm."
|
|
1492
|
-
6. If publishing_rules.allow_immediate_send is false and the user asks to send immediately, suggest scheduling instead
|
|
1493
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview and subscriber count
|
|
1494
|
-
|
|
1495
|
-
When confirmed=false, this tool returns a structured preview with broadcast details, subscriber count, safety checks, and a confirmation message. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.`,
|
|
1046
|
+
"Send a newsletter to subscribers. This action cannot be undone. Set confirmed=false to preview first.",
|
|
1496
1047
|
{
|
|
1497
1048
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1498
1049
|
confirmed: z5.boolean().default(false).describe(
|
|
@@ -1556,22 +1107,7 @@ Call this tool again with confirmed=true to send.`;
|
|
|
1556
1107
|
}
|
|
1557
1108
|
server2.tool(
|
|
1558
1109
|
"schedule_newsletter",
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
REQUIRED WORKFLOW:
|
|
1562
|
-
1. ALWAYS call get_publishing_rules first
|
|
1563
|
-
2. NEVER schedule without showing a full visual preview of the newsletter first
|
|
1564
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt with details, not an actual schedule
|
|
1565
|
-
4. Tell the user exactly how many subscribers will receive this email and the scheduled send time
|
|
1566
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1567
|
-
- First: "Are you sure you want to schedule this for [time] to [X] subscribers?"
|
|
1568
|
-
- Second: "Confirm schedule for [time]."
|
|
1569
|
-
6. If publishing_rules.allow_immediate_send is false and the scheduled time is very soon, warn the user
|
|
1570
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview, subscriber count, and scheduled time
|
|
1571
|
-
|
|
1572
|
-
Scheduling can typically be cancelled or rescheduled later, but the user should still verify the time is correct before confirming.
|
|
1573
|
-
|
|
1574
|
-
When confirmed=false, this tool returns a structured preview with broadcast details, scheduled time, subscriber count, safety checks, and a confirmation message. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.`,
|
|
1110
|
+
"Schedule a newsletter for future send. Set confirmed=false to preview first.",
|
|
1575
1111
|
{
|
|
1576
1112
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to schedule"),
|
|
1577
1113
|
scheduled_for: z5.string().describe(
|
|
@@ -1647,7 +1183,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1647
1183
|
);
|
|
1648
1184
|
server2.tool(
|
|
1649
1185
|
"list_newsletters",
|
|
1650
|
-
"List
|
|
1186
|
+
"List newsletters with status, dates, and subject. Supports filtering by status, date range, and keyword.",
|
|
1651
1187
|
{
|
|
1652
1188
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
1653
1189
|
status: z5.enum(["draft", "sent", "scheduled"]).optional().describe("Filter by status: draft, sent, or scheduled"),
|
|
@@ -1695,7 +1231,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1695
1231
|
);
|
|
1696
1232
|
server2.tool(
|
|
1697
1233
|
"list_tags",
|
|
1698
|
-
"List all subscriber tags
|
|
1234
|
+
"List all subscriber tags.",
|
|
1699
1235
|
{},
|
|
1700
1236
|
{
|
|
1701
1237
|
title: "List Subscriber Tags",
|
|
@@ -1711,27 +1247,9 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1711
1247
|
};
|
|
1712
1248
|
}
|
|
1713
1249
|
);
|
|
1714
|
-
server2.tool(
|
|
1715
|
-
"list_sequences",
|
|
1716
|
-
"List all email sequences/automations from your email service provider. On Beehiiv, this returns automations. For detailed automation data use list_automations instead.",
|
|
1717
|
-
{},
|
|
1718
|
-
{
|
|
1719
|
-
title: "List Email Sequences",
|
|
1720
|
-
readOnlyHint: true,
|
|
1721
|
-
destructiveHint: false,
|
|
1722
|
-
idempotentHint: true,
|
|
1723
|
-
openWorldHint: true
|
|
1724
|
-
},
|
|
1725
|
-
async () => {
|
|
1726
|
-
const result = await client2.listSequences();
|
|
1727
|
-
return {
|
|
1728
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
);
|
|
1732
1250
|
server2.tool(
|
|
1733
1251
|
"list_forms",
|
|
1734
|
-
"List all signup forms
|
|
1252
|
+
"List all signup forms.",
|
|
1735
1253
|
{},
|
|
1736
1254
|
{
|
|
1737
1255
|
title: "List Signup Forms",
|
|
@@ -1754,7 +1272,7 @@ import { z as z6 } from "zod";
|
|
|
1754
1272
|
function registerRssTools(server2, client2) {
|
|
1755
1273
|
server2.tool(
|
|
1756
1274
|
"fetch_feed",
|
|
1757
|
-
"Fetch and parse entries from an RSS or Atom feed URL.
|
|
1275
|
+
"Fetch and parse entries from an RSS or Atom feed URL.",
|
|
1758
1276
|
{
|
|
1759
1277
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
1760
1278
|
limit: z6.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
@@ -1775,7 +1293,7 @@ function registerRssTools(server2, client2) {
|
|
|
1775
1293
|
);
|
|
1776
1294
|
server2.tool(
|
|
1777
1295
|
"fetch_article",
|
|
1778
|
-
"Extract
|
|
1296
|
+
"Extract full article content from a URL as clean text.",
|
|
1779
1297
|
{
|
|
1780
1298
|
url: z6.string().describe("The article URL to extract content from")
|
|
1781
1299
|
},
|
|
@@ -1799,7 +1317,7 @@ function registerRssTools(server2, client2) {
|
|
|
1799
1317
|
function registerAccountInfoTool(server2, client2) {
|
|
1800
1318
|
server2.tool(
|
|
1801
1319
|
"get_account",
|
|
1802
|
-
"Get
|
|
1320
|
+
"Get account details, subscription status, ESP config, and all connected social/ESP accounts.",
|
|
1803
1321
|
{},
|
|
1804
1322
|
{
|
|
1805
1323
|
title: "Get Account Details",
|
|
@@ -1809,9 +1327,15 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
1809
1327
|
openWorldHint: false
|
|
1810
1328
|
},
|
|
1811
1329
|
async () => {
|
|
1812
|
-
const
|
|
1330
|
+
const [account, accountsData] = await Promise.all([
|
|
1331
|
+
client2.getAccount(),
|
|
1332
|
+
client2.listAccounts()
|
|
1333
|
+
]);
|
|
1813
1334
|
return {
|
|
1814
|
-
content: [{
|
|
1335
|
+
content: [{
|
|
1336
|
+
type: "text",
|
|
1337
|
+
text: JSON.stringify({ account, connectedAccounts: accountsData }, null, 2)
|
|
1338
|
+
}]
|
|
1815
1339
|
};
|
|
1816
1340
|
}
|
|
1817
1341
|
);
|
|
@@ -1822,7 +1346,7 @@ import { z as z7 } from "zod";
|
|
|
1822
1346
|
function registerBrandVoiceTools(server2, client2) {
|
|
1823
1347
|
server2.tool(
|
|
1824
1348
|
"get_brand_voice",
|
|
1825
|
-
"Get the
|
|
1349
|
+
"Get the brand voice profile and writing rules. Call before creating content.",
|
|
1826
1350
|
{},
|
|
1827
1351
|
{
|
|
1828
1352
|
title: "Get Brand Voice",
|
|
@@ -1897,21 +1421,14 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1897
1421
|
);
|
|
1898
1422
|
server2.tool(
|
|
1899
1423
|
"update_brand_voice",
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
-
|
|
1907
|
-
|
|
1908
|
-
{
|
|
1909
|
-
name: z7.string().max(80).optional().describe("Brand voice profile name (e.g. 'BOQtoday Voice', 'Lightbreak Editorial')"),
|
|
1910
|
-
description: z7.string().max(16e3).optional().describe("Overall voice description \u2014 tone, personality, style overview"),
|
|
1911
|
-
dos: z7.array(z7.string()).max(50).optional().describe("Writing rules to follow (array of do's). Send the FULL list, not just additions."),
|
|
1912
|
-
donts: z7.array(z7.string()).max(50).optional().describe("Things to avoid (array of don'ts). Send the FULL list, not just additions."),
|
|
1913
|
-
platform_rules: z7.record(z7.string()).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars, punchy tone", "linkedin": "Professional, add hashtags" }'),
|
|
1914
|
-
example_posts: z7.array(z7.string()).max(8).optional().describe("Example posts that demonstrate the desired voice (max 8)"),
|
|
1424
|
+
"Update the brand voice profile. Set confirmed=false to preview first.",
|
|
1425
|
+
{
|
|
1426
|
+
name: z7.string().max(80).optional().describe("Brand voice profile name"),
|
|
1427
|
+
description: z7.string().max(2e3).optional().describe("Overall voice description"),
|
|
1428
|
+
dos: z7.array(z7.string().max(200)).max(15).optional().describe("Writing rules to follow. Send the FULL list, not just additions."),
|
|
1429
|
+
donts: z7.array(z7.string().max(200)).max(15).optional().describe("Things to avoid. Send the FULL list, not just additions."),
|
|
1430
|
+
platform_rules: z7.record(z7.string().max(300)).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars" }'),
|
|
1431
|
+
example_posts: z7.array(z7.string().max(500)).max(5).optional().describe("Example posts that demonstrate the desired voice"),
|
|
1915
1432
|
confirmed: z7.boolean().default(false).describe(
|
|
1916
1433
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
1917
1434
|
)
|
|
@@ -1929,7 +1446,19 @@ USAGE GUIDELINES:
|
|
|
1929
1446
|
if (args.description !== void 0) payload.description = args.description;
|
|
1930
1447
|
if (args.dos !== void 0) payload.dos = args.dos;
|
|
1931
1448
|
if (args.donts !== void 0) payload.donts = args.donts;
|
|
1932
|
-
if (args.platform_rules !== void 0)
|
|
1449
|
+
if (args.platform_rules !== void 0) {
|
|
1450
|
+
const entries = Object.entries(args.platform_rules);
|
|
1451
|
+
if (entries.length > 5) {
|
|
1452
|
+
return {
|
|
1453
|
+
content: [{
|
|
1454
|
+
type: "text",
|
|
1455
|
+
text: "Maximum 5 platform rules allowed."
|
|
1456
|
+
}],
|
|
1457
|
+
isError: true
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
payload.platformRules = args.platform_rules;
|
|
1461
|
+
}
|
|
1933
1462
|
if (args.example_posts !== void 0) payload.examplePosts = args.example_posts;
|
|
1934
1463
|
if (Object.keys(payload).length === 0) {
|
|
1935
1464
|
return {
|
|
@@ -2017,7 +1546,7 @@ import { z as z8 } from "zod";
|
|
|
2017
1546
|
function registerKnowledgeTools(server2, client2) {
|
|
2018
1547
|
server2.tool(
|
|
2019
1548
|
"get_knowledge_base",
|
|
2020
|
-
"Get all items from the
|
|
1549
|
+
"Get all items from the knowledge base.",
|
|
2021
1550
|
{},
|
|
2022
1551
|
{
|
|
2023
1552
|
title: "Get Knowledge Base",
|
|
@@ -2071,7 +1600,7 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
2071
1600
|
);
|
|
2072
1601
|
server2.tool(
|
|
2073
1602
|
"search_knowledge",
|
|
2074
|
-
"Search the
|
|
1603
|
+
"Search the knowledge base by tag or keyword.",
|
|
2075
1604
|
{
|
|
2076
1605
|
query: z8.string().describe(
|
|
2077
1606
|
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
@@ -2122,7 +1651,7 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
2122
1651
|
);
|
|
2123
1652
|
server2.tool(
|
|
2124
1653
|
"add_knowledge",
|
|
2125
|
-
"Add reference material to the
|
|
1654
|
+
"Add reference material to the knowledge base. Include tags for categorization.",
|
|
2126
1655
|
{
|
|
2127
1656
|
title: z8.string().describe("Title for the knowledge item"),
|
|
2128
1657
|
content: z8.string().describe("The content/text to save"),
|
|
@@ -2164,10 +1693,8 @@ import { z as z9 } from "zod";
|
|
|
2164
1693
|
function registerAudienceTools(server2, client2) {
|
|
2165
1694
|
server2.tool(
|
|
2166
1695
|
"get_audience",
|
|
2167
|
-
"Get the target audience profile
|
|
2168
|
-
{
|
|
2169
|
-
audienceId: z9.string().optional().describe("Specific audience profile ID. If omitted, returns the default audience.")
|
|
2170
|
-
},
|
|
1696
|
+
"Get the target audience profile. Call before creating content.",
|
|
1697
|
+
{},
|
|
2171
1698
|
{
|
|
2172
1699
|
title: "Get Audience Profile",
|
|
2173
1700
|
readOnlyHint: true,
|
|
@@ -2175,9 +1702,9 @@ function registerAudienceTools(server2, client2) {
|
|
|
2175
1702
|
idempotentHint: true,
|
|
2176
1703
|
openWorldHint: false
|
|
2177
1704
|
},
|
|
2178
|
-
async (
|
|
1705
|
+
async () => {
|
|
2179
1706
|
try {
|
|
2180
|
-
const audience =
|
|
1707
|
+
const audience = await client2.getDefaultAudience();
|
|
2181
1708
|
const lines = [];
|
|
2182
1709
|
lines.push(`## Target Audience: ${audience.name}`);
|
|
2183
1710
|
lines.push("");
|
|
@@ -2241,25 +1768,16 @@ function registerAudienceTools(server2, client2) {
|
|
|
2241
1768
|
);
|
|
2242
1769
|
server2.tool(
|
|
2243
1770
|
"update_audience",
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
USAGE GUIDELINES:
|
|
2247
|
-
- Call get_audience first to see the current state before making changes
|
|
2248
|
-
- Always set confirmed=false first to preview the changes, then confirmed=true after user approval
|
|
2249
|
-
- When updating array fields (pain_points, motivations, preferred_platforms), include the FULL array (existing + new items), not just the new ones
|
|
2250
|
-
- Omit audience_id to update the default audience or create a new one if none exists
|
|
2251
|
-
- Max limits: name 100 chars, description 500 chars`,
|
|
1771
|
+
"Update the audience profile. Set confirmed=false to preview first.",
|
|
2252
1772
|
{
|
|
2253
|
-
|
|
2254
|
-
name: z9.string().max(100).optional().describe("Audience profile name (e.g. 'SaaS Founders', 'Health-Conscious Millennials'). Required when creating a new audience."),
|
|
1773
|
+
name: z9.string().max(100).optional().describe("Audience profile name. Required when creating a new audience."),
|
|
2255
1774
|
description: z9.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
2256
|
-
demographics: z9.string().optional().describe("Demographic details
|
|
2257
|
-
pain_points: z9.array(z9.string()).optional().describe("Problems
|
|
2258
|
-
motivations: z9.array(z9.string()).optional().describe("Goals
|
|
2259
|
-
preferred_platforms: z9.array(z9.string()).optional().describe("Social platforms the audience is most active on
|
|
2260
|
-
tone_notes: z9.string().optional().describe("How to speak to this audience
|
|
2261
|
-
content_preferences: z9.string().optional().describe("What content formats and topics resonate
|
|
2262
|
-
is_default: z9.boolean().optional().describe("Set to true to make this the default audience profile"),
|
|
1775
|
+
demographics: z9.string().max(500).optional().describe("Demographic details"),
|
|
1776
|
+
pain_points: z9.array(z9.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
1777
|
+
motivations: z9.array(z9.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
1778
|
+
preferred_platforms: z9.array(z9.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
1779
|
+
tone_notes: z9.string().max(500).optional().describe("How to speak to this audience"),
|
|
1780
|
+
content_preferences: z9.string().max(500).optional().describe("What content formats and topics resonate"),
|
|
2263
1781
|
confirmed: z9.boolean().default(false).describe(
|
|
2264
1782
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
2265
1783
|
)
|
|
@@ -2281,27 +1799,24 @@ USAGE GUIDELINES:
|
|
|
2281
1799
|
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
2282
1800
|
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
2283
1801
|
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2284
|
-
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
2285
1802
|
if (Object.keys(payload).length === 0) {
|
|
2286
1803
|
return {
|
|
2287
1804
|
content: [
|
|
2288
1805
|
{
|
|
2289
1806
|
type: "text",
|
|
2290
|
-
text: "No fields provided. Specify at least one field to update
|
|
1807
|
+
text: "No fields provided. Specify at least one field to update."
|
|
2291
1808
|
}
|
|
2292
1809
|
],
|
|
2293
1810
|
isError: true
|
|
2294
1811
|
};
|
|
2295
1812
|
}
|
|
2296
|
-
let targetId
|
|
1813
|
+
let targetId;
|
|
2297
1814
|
let isCreating = false;
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
isCreating = true;
|
|
2304
|
-
}
|
|
1815
|
+
try {
|
|
1816
|
+
const defaultAudience = await client2.getDefaultAudience();
|
|
1817
|
+
targetId = String(defaultAudience.id);
|
|
1818
|
+
} catch {
|
|
1819
|
+
isCreating = true;
|
|
2305
1820
|
}
|
|
2306
1821
|
if (isCreating && !payload.name) {
|
|
2307
1822
|
return {
|
|
@@ -2317,9 +1832,6 @@ USAGE GUIDELINES:
|
|
|
2317
1832
|
if (args.confirmed !== true) {
|
|
2318
1833
|
const lines2 = [];
|
|
2319
1834
|
lines2.push(isCreating ? "## New Audience Preview" : "## Audience Update Preview");
|
|
2320
|
-
if (!isCreating) {
|
|
2321
|
-
lines2.push(`**Audience ID:** ${targetId}`);
|
|
2322
|
-
}
|
|
2323
1835
|
lines2.push("");
|
|
2324
1836
|
if (payload.name) {
|
|
2325
1837
|
lines2.push(`**Name:** ${payload.name}`);
|
|
@@ -2367,10 +1879,6 @@ USAGE GUIDELINES:
|
|
|
2367
1879
|
lines2.push(String(payload.contentPreferences));
|
|
2368
1880
|
lines2.push("");
|
|
2369
1881
|
}
|
|
2370
|
-
if (payload.isDefault !== void 0) {
|
|
2371
|
-
lines2.push(`**Set as default:** ${payload.isDefault ? "Yes" : "No"}`);
|
|
2372
|
-
lines2.push("");
|
|
2373
|
-
}
|
|
2374
1882
|
lines2.push("---");
|
|
2375
1883
|
lines2.push("Call this tool again with **confirmed=true** to save these changes.");
|
|
2376
1884
|
return {
|
|
@@ -2396,96 +1904,12 @@ USAGE GUIDELINES:
|
|
|
2396
1904
|
if (payload.platforms) summary.push(`${payload.platforms.length} platforms`);
|
|
2397
1905
|
if (payload.toneNotes) summary.push("tone notes");
|
|
2398
1906
|
if (payload.contentPreferences) summary.push("content preferences");
|
|
2399
|
-
if (payload.isDefault !== void 0) summary.push("default status");
|
|
2400
1907
|
lines.push(`**${isCreating ? "Created with" : "Updated"}:** ${summary.join(", ")}`);
|
|
2401
1908
|
return {
|
|
2402
1909
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
2403
1910
|
};
|
|
2404
1911
|
}
|
|
2405
1912
|
);
|
|
2406
|
-
server2.tool(
|
|
2407
|
-
"delete_audience",
|
|
2408
|
-
`Permanently delete a target audience profile by ID. This action cannot be undone.
|
|
2409
|
-
|
|
2410
|
-
REQUIRED WORKFLOW:
|
|
2411
|
-
1. ALWAYS call with confirmed=false first to preview which audience will be deleted
|
|
2412
|
-
2. Show the user the audience name and details
|
|
2413
|
-
3. Only call again with confirmed=true after explicit user approval
|
|
2414
|
-
|
|
2415
|
-
SAFETY: Cannot delete the default audience if it is the only audience profile. The customer must create another audience first.`,
|
|
2416
|
-
{
|
|
2417
|
-
audience_id: z9.string().describe("The ID of the audience profile to delete"),
|
|
2418
|
-
confirmed: z9.boolean().default(false).describe(
|
|
2419
|
-
"Set to true to confirm deletion after previewing. If false or missing, returns a preview for user approval."
|
|
2420
|
-
)
|
|
2421
|
-
},
|
|
2422
|
-
{
|
|
2423
|
-
title: "Delete Audience Profile",
|
|
2424
|
-
readOnlyHint: false,
|
|
2425
|
-
destructiveHint: true,
|
|
2426
|
-
idempotentHint: true,
|
|
2427
|
-
openWorldHint: false
|
|
2428
|
-
},
|
|
2429
|
-
async (args) => {
|
|
2430
|
-
let audience;
|
|
2431
|
-
try {
|
|
2432
|
-
audience = await client2.getAudience(args.audience_id);
|
|
2433
|
-
} catch (error) {
|
|
2434
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2435
|
-
if (message.includes("404") || message.includes("not found")) {
|
|
2436
|
-
return {
|
|
2437
|
-
content: [
|
|
2438
|
-
{
|
|
2439
|
-
type: "text",
|
|
2440
|
-
text: `Audience with ID ${args.audience_id} not found.`
|
|
2441
|
-
}
|
|
2442
|
-
],
|
|
2443
|
-
isError: true
|
|
2444
|
-
};
|
|
2445
|
-
}
|
|
2446
|
-
throw error;
|
|
2447
|
-
}
|
|
2448
|
-
if (audience.isDefault) {
|
|
2449
|
-
const allAudiences = await client2.listAudiences();
|
|
2450
|
-
if (allAudiences.length <= 1) {
|
|
2451
|
-
return {
|
|
2452
|
-
content: [
|
|
2453
|
-
{
|
|
2454
|
-
type: "text",
|
|
2455
|
-
text: `Cannot delete "${audience.name}" because it is the only audience profile. Create another audience before deleting this one.`
|
|
2456
|
-
}
|
|
2457
|
-
],
|
|
2458
|
-
isError: true
|
|
2459
|
-
};
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
if (args.confirmed !== true) {
|
|
2463
|
-
const lines = [];
|
|
2464
|
-
lines.push("## Delete Audience Confirmation");
|
|
2465
|
-
lines.push("");
|
|
2466
|
-
lines.push(`**Name:** ${audience.name}`);
|
|
2467
|
-
lines.push(`**ID:** ${audience.id}`);
|
|
2468
|
-
if (audience.isDefault) lines.push("**Default:** Yes");
|
|
2469
|
-
if (audience.description) lines.push(`**Description:** ${audience.description}`);
|
|
2470
|
-
lines.push("");
|
|
2471
|
-
lines.push(
|
|
2472
|
-
"**This action is permanent and cannot be undone.** Call this tool again with confirmed=true to proceed."
|
|
2473
|
-
);
|
|
2474
|
-
return {
|
|
2475
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
2476
|
-
};
|
|
2477
|
-
}
|
|
2478
|
-
await client2.deleteAudience(args.audience_id);
|
|
2479
|
-
return {
|
|
2480
|
-
content: [
|
|
2481
|
-
{
|
|
2482
|
-
type: "text",
|
|
2483
|
-
text: `Audience "${audience.name}" (ID: ${audience.id}) has been permanently deleted.`
|
|
2484
|
-
}
|
|
2485
|
-
]
|
|
2486
|
-
};
|
|
2487
|
-
}
|
|
2488
|
-
);
|
|
2489
1913
|
}
|
|
2490
1914
|
|
|
2491
1915
|
// src/tools/newsletter-template.ts
|
|
@@ -2523,7 +1947,7 @@ Follow these rules when drafting:
|
|
|
2523
1947
|
function registerNewsletterTemplateTools(server2, client2) {
|
|
2524
1948
|
server2.tool(
|
|
2525
1949
|
"get_newsletter_template",
|
|
2526
|
-
"Get
|
|
1950
|
+
"Get a newsletter template's structure, sections, and HTML. Pass templateId or omit for default.",
|
|
2527
1951
|
{
|
|
2528
1952
|
templateId: z10.string().optional().describe(
|
|
2529
1953
|
"Specific template ID. If omitted, returns the default template."
|
|
@@ -2669,9 +2093,10 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2669
2093
|
);
|
|
2670
2094
|
server2.tool(
|
|
2671
2095
|
"get_past_newsletters",
|
|
2672
|
-
"
|
|
2096
|
+
"Get past newsletters from the archive. Pass newsletterId to get full content of a specific edition.",
|
|
2673
2097
|
{
|
|
2674
|
-
|
|
2098
|
+
newsletterId: z10.string().optional().describe("If provided, returns the full content of this specific newsletter. If omitted, returns the list."),
|
|
2099
|
+
limit: z10.number().optional().describe("Number of past newsletters to retrieve (when listing). Default 5, max 50."),
|
|
2675
2100
|
templateId: z10.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
2676
2101
|
},
|
|
2677
2102
|
{
|
|
@@ -2681,7 +2106,41 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2681
2106
|
idempotentHint: true,
|
|
2682
2107
|
openWorldHint: false
|
|
2683
2108
|
},
|
|
2684
|
-
async ({ limit, templateId }) => {
|
|
2109
|
+
async ({ newsletterId, limit, templateId }) => {
|
|
2110
|
+
if (newsletterId) {
|
|
2111
|
+
try {
|
|
2112
|
+
const newsletter = await client2.getArchivedNewsletter(
|
|
2113
|
+
newsletterId
|
|
2114
|
+
);
|
|
2115
|
+
const lines = [];
|
|
2116
|
+
lines.push(`## ${newsletter.subject}`);
|
|
2117
|
+
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
2118
|
+
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
2119
|
+
lines.push("");
|
|
2120
|
+
lines.push("### Full HTML Content:");
|
|
2121
|
+
lines.push(newsletter.contentHtml);
|
|
2122
|
+
return {
|
|
2123
|
+
content: [
|
|
2124
|
+
{ type: "text", text: lines.join("\n") },
|
|
2125
|
+
...newsletter.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2126
|
+
${newsletter.contentHtml}` }] : []
|
|
2127
|
+
]
|
|
2128
|
+
};
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2131
|
+
if (message.includes("404")) {
|
|
2132
|
+
return {
|
|
2133
|
+
content: [
|
|
2134
|
+
{
|
|
2135
|
+
type: "text",
|
|
2136
|
+
text: "Newsletter not found. Check the ID and try again."
|
|
2137
|
+
}
|
|
2138
|
+
]
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
throw error;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2685
2144
|
try {
|
|
2686
2145
|
const params = {};
|
|
2687
2146
|
if (limit) params.limit = String(Math.min(limit, 50));
|
|
@@ -2719,7 +2178,7 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2719
2178
|
const textContent = nl.contentHtml ? nl.contentHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 500) : "(no content)";
|
|
2720
2179
|
lines.push(textContent);
|
|
2721
2180
|
lines.push(
|
|
2722
|
-
`[Full content available
|
|
2181
|
+
`[Full content available by calling get_past_newsletters with newsletterId: ${nl.id}]`
|
|
2723
2182
|
);
|
|
2724
2183
|
lines.push("");
|
|
2725
2184
|
}
|
|
@@ -2737,57 +2196,9 @@ ${mostRecent.contentHtml}` }] : []
|
|
|
2737
2196
|
}
|
|
2738
2197
|
}
|
|
2739
2198
|
);
|
|
2740
|
-
server2.tool(
|
|
2741
|
-
"get_past_newsletter",
|
|
2742
|
-
"Get the full content of a specific past newsletter by ID. Use when you need to reference the complete text of a previous edition, check exact formatting, or continue a series.",
|
|
2743
|
-
{
|
|
2744
|
-
newsletterId: z10.string().describe("The ID of the archived newsletter to retrieve.")
|
|
2745
|
-
},
|
|
2746
|
-
{
|
|
2747
|
-
title: "Get Past Newsletter Detail",
|
|
2748
|
-
readOnlyHint: true,
|
|
2749
|
-
destructiveHint: false,
|
|
2750
|
-
idempotentHint: true,
|
|
2751
|
-
openWorldHint: false
|
|
2752
|
-
},
|
|
2753
|
-
async ({ newsletterId }) => {
|
|
2754
|
-
try {
|
|
2755
|
-
const newsletter = await client2.getArchivedNewsletter(
|
|
2756
|
-
newsletterId
|
|
2757
|
-
);
|
|
2758
|
-
const lines = [];
|
|
2759
|
-
lines.push(`## ${newsletter.subject}`);
|
|
2760
|
-
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
2761
|
-
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
2762
|
-
lines.push("");
|
|
2763
|
-
lines.push("### Full HTML Content:");
|
|
2764
|
-
lines.push(newsletter.contentHtml);
|
|
2765
|
-
return {
|
|
2766
|
-
content: [
|
|
2767
|
-
{ type: "text", text: lines.join("\n") },
|
|
2768
|
-
...newsletter.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2769
|
-
${newsletter.contentHtml}` }] : []
|
|
2770
|
-
]
|
|
2771
|
-
};
|
|
2772
|
-
} catch (error) {
|
|
2773
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2774
|
-
if (message.includes("404")) {
|
|
2775
|
-
return {
|
|
2776
|
-
content: [
|
|
2777
|
-
{
|
|
2778
|
-
type: "text",
|
|
2779
|
-
text: "Newsletter not found. Check the ID and try again."
|
|
2780
|
-
}
|
|
2781
|
-
]
|
|
2782
|
-
};
|
|
2783
|
-
}
|
|
2784
|
-
throw error;
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
);
|
|
2788
2199
|
server2.tool(
|
|
2789
2200
|
"save_newsletter",
|
|
2790
|
-
"Save
|
|
2201
|
+
"Save an approved newsletter to the archive for future reference.",
|
|
2791
2202
|
{
|
|
2792
2203
|
subject: z10.string().describe("The newsletter subject line."),
|
|
2793
2204
|
contentHtml: z10.string().describe("The full HTML content of the newsletter."),
|
|
@@ -2830,17 +2241,15 @@ ${contentHtml}` }] : []
|
|
|
2830
2241
|
);
|
|
2831
2242
|
server2.tool(
|
|
2832
2243
|
"save_newsletter_template",
|
|
2833
|
-
"
|
|
2244
|
+
"Create or update a newsletter template with HTML and section definitions.",
|
|
2834
2245
|
{
|
|
2835
2246
|
template_id: z10.string().optional().describe(
|
|
2836
2247
|
"If provided, updates this existing template. Otherwise creates a new one."
|
|
2837
2248
|
),
|
|
2838
|
-
name: z10.string().min(1).max(255).describe("Template name
|
|
2839
|
-
description: z10.string().max(2e3).optional().describe(
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
html_content: z10.string().min(1).describe(
|
|
2843
|
-
"Full HTML template with {{placeholder}} variables. This is the email body HTML with inline styles."
|
|
2249
|
+
name: z10.string().min(1).max(255).describe("Template name"),
|
|
2250
|
+
description: z10.string().max(2e3).optional().describe("What this template is for"),
|
|
2251
|
+
html_content: z10.string().min(1).max(1e5).describe(
|
|
2252
|
+
"Full HTML template with {{placeholder}} variables."
|
|
2844
2253
|
),
|
|
2845
2254
|
sections: z10.array(
|
|
2846
2255
|
z10.object({
|
|
@@ -2849,7 +2258,7 @@ ${contentHtml}` }] : []
|
|
|
2849
2258
|
required: z10.boolean().optional().describe("Whether this section is required"),
|
|
2850
2259
|
item_count: z10.number().optional().describe("Number of items in this section, if applicable")
|
|
2851
2260
|
})
|
|
2852
|
-
).optional().describe("Array of section metadata describing the template structure"),
|
|
2261
|
+
).max(20).optional().describe("Array of section metadata describing the template structure"),
|
|
2853
2262
|
style_config: z10.object({
|
|
2854
2263
|
primary_color: z10.string().optional(),
|
|
2855
2264
|
accent_color: z10.string().optional(),
|
|
@@ -2859,7 +2268,7 @@ ${contentHtml}` }] : []
|
|
|
2859
2268
|
max_width: z10.string().optional()
|
|
2860
2269
|
}).optional().describe("Colors, fonts, and brand configuration"),
|
|
2861
2270
|
is_default: z10.boolean().optional().describe(
|
|
2862
|
-
"Set as the default template for this customer.
|
|
2271
|
+
"Set as the default template for this customer."
|
|
2863
2272
|
)
|
|
2864
2273
|
},
|
|
2865
2274
|
{
|
|
@@ -2930,7 +2339,7 @@ ${contentHtml}` }] : []
|
|
|
2930
2339
|
);
|
|
2931
2340
|
server2.tool(
|
|
2932
2341
|
"list_newsletter_templates",
|
|
2933
|
-
"List all saved newsletter templates
|
|
2342
|
+
"List all saved newsletter templates.",
|
|
2934
2343
|
{},
|
|
2935
2344
|
{
|
|
2936
2345
|
title: "List Newsletter Templates",
|
|
@@ -2980,7 +2389,7 @@ ${contentHtml}` }] : []
|
|
|
2980
2389
|
);
|
|
2981
2390
|
server2.tool(
|
|
2982
2391
|
"delete_newsletter_template",
|
|
2983
|
-
"Delete a newsletter template.
|
|
2392
|
+
"Delete a newsletter template. Set confirmed=true to proceed.",
|
|
2984
2393
|
{
|
|
2985
2394
|
template_id: z10.string().describe("The ID of the newsletter template to delete."),
|
|
2986
2395
|
confirmed: z10.boolean().default(false).describe(
|
|
@@ -3039,7 +2448,7 @@ import { z as z11 } from "zod";
|
|
|
3039
2448
|
function registerCalendarTools(server2, client2) {
|
|
3040
2449
|
server2.tool(
|
|
3041
2450
|
"get_calendar",
|
|
3042
|
-
"View the
|
|
2451
|
+
"View the content calendar with all scheduled, published, and draft posts.",
|
|
3043
2452
|
{
|
|
3044
2453
|
from: z11.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
3045
2454
|
to: z11.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
@@ -3122,7 +2531,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
3122
2531
|
);
|
|
3123
2532
|
server2.tool(
|
|
3124
2533
|
"get_queue",
|
|
3125
|
-
"View the
|
|
2534
|
+
"View the posting queue schedule and recurring weekly time slots.",
|
|
3126
2535
|
{},
|
|
3127
2536
|
{
|
|
3128
2537
|
title: "Get Posting Queue",
|
|
@@ -3178,19 +2587,7 @@ Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
|
3178
2587
|
);
|
|
3179
2588
|
server2.tool(
|
|
3180
2589
|
"schedule_to_queue",
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
Add a post to the customer's content queue for automatic scheduling.
|
|
3184
|
-
|
|
3185
|
-
Always show the user a preview of the content before calling this tool. Call get_brand_voice and get_audience before composing if not already loaded this session. Never call with confirmed=true without explicit user approval.
|
|
3186
|
-
|
|
3187
|
-
REQUIRED WORKFLOW:
|
|
3188
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
3189
|
-
2. ALWAYS set confirmed=false first to preview
|
|
3190
|
-
3. Show the user a visual preview before confirming
|
|
3191
|
-
4. Tell the user which queue slot the post will be assigned to
|
|
3192
|
-
|
|
3193
|
-
When confirmed=false, this tool returns a structured preview with content, platform details, safety checks, and the assigned queue slot. You MUST render this as a visual preview for the user and wait for their explicit approval before calling again with confirmed=true.`,
|
|
2590
|
+
"Add a post to the next available queue slot. Check get_queue first. Set confirmed=false to preview first.",
|
|
3194
2591
|
{
|
|
3195
2592
|
content: z11.string().describe("The text content of the post"),
|
|
3196
2593
|
platforms: z11.array(z11.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -3291,7 +2688,7 @@ function timeAgo(dateStr) {
|
|
|
3291
2688
|
function registerNotificationTools(server2, client2) {
|
|
3292
2689
|
server2.tool(
|
|
3293
2690
|
"get_notifications",
|
|
3294
|
-
"Get recent notifications including post
|
|
2691
|
+
"Get recent notifications including post results, account warnings, and errors.",
|
|
3295
2692
|
{
|
|
3296
2693
|
unread_only: z12.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
3297
2694
|
limit: z12.number().optional().describe("Max notifications to return. Default 10.")
|
|
@@ -3336,7 +2733,7 @@ No notifications found. All clear!`
|
|
|
3336
2733
|
const action = n.actionLabel || "View";
|
|
3337
2734
|
if (n.resourceType === "post" && n.resourceId) {
|
|
3338
2735
|
line += `
|
|
3339
|
-
Action: ${action} \u2192
|
|
2736
|
+
Action: ${action} \u2192 get_post with post_id "${n.resourceId}"`;
|
|
3340
2737
|
} else {
|
|
3341
2738
|
line += `
|
|
3342
2739
|
Action: ${action} \u2192 ${n.actionUrl}`;
|
|
@@ -3361,10 +2758,36 @@ Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" :
|
|
|
3361
2758
|
}
|
|
3362
2759
|
|
|
3363
2760
|
// src/tools/publishing-rules.ts
|
|
2761
|
+
var WORKFLOW = {
|
|
2762
|
+
before_writing_content: [
|
|
2763
|
+
"Call get_brand_voice to load tone and style rules",
|
|
2764
|
+
"Call get_audience to understand who the content is for",
|
|
2765
|
+
"For newsletters: also call get_newsletter_template and get_past_newsletters"
|
|
2766
|
+
],
|
|
2767
|
+
before_publishing: [
|
|
2768
|
+
"Always set confirmed=false first to generate a preview",
|
|
2769
|
+
"Show the user a visual preview before confirming",
|
|
2770
|
+
"Never set confirmed=true without explicit user approval",
|
|
2771
|
+
"For newsletters: show subscriber count and send time before confirming",
|
|
2772
|
+
"If require_double_confirm is true, ask for confirmation twice"
|
|
2773
|
+
],
|
|
2774
|
+
replies_and_engagement: [
|
|
2775
|
+
"Draft the reply and show it before sending",
|
|
2776
|
+
"Set confirmed=false first to preview",
|
|
2777
|
+
"Remind user that replies are public"
|
|
2778
|
+
],
|
|
2779
|
+
newsletters: [
|
|
2780
|
+
"Always render newsletter as HTML artifact/preview before pushing to ESP",
|
|
2781
|
+
"Use table-based layouts, inline CSS only, 600px max width",
|
|
2782
|
+
"Email-safe fonts only: Arial, Helvetica, Georgia, Verdana",
|
|
2783
|
+
"All images must use absolute URLs",
|
|
2784
|
+
"Keep total email under 102KB to avoid Gmail clipping"
|
|
2785
|
+
]
|
|
2786
|
+
};
|
|
3364
2787
|
function registerPublishingRulesTools(server2, client2) {
|
|
3365
2788
|
server2.tool(
|
|
3366
2789
|
"get_publishing_rules",
|
|
3367
|
-
|
|
2790
|
+
"Get publishing rules and the content creation workflow. Call this BEFORE publishing, scheduling, or sending any content.",
|
|
3368
2791
|
{},
|
|
3369
2792
|
{
|
|
3370
2793
|
title: "Get Publishing Rules",
|
|
@@ -3375,90 +2798,30 @@ function registerPublishingRulesTools(server2, client2) {
|
|
|
3375
2798
|
},
|
|
3376
2799
|
async () => {
|
|
3377
2800
|
const rules = await client2.getPublishingRules();
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
`;
|
|
3397
|
-
if (rules.blockedWords && rules.blockedWords.length > 0) {
|
|
3398
|
-
text += `Blocked words: **${rules.blockedWords.join(", ")}**
|
|
3399
|
-
`;
|
|
3400
|
-
} else {
|
|
3401
|
-
text += "Blocked words: none\n";
|
|
3402
|
-
}
|
|
3403
|
-
if (rules.requiredDisclaimer) {
|
|
3404
|
-
text += `Required disclaimer: "${rules.requiredDisclaimer}"
|
|
3405
|
-
`;
|
|
3406
|
-
} else {
|
|
3407
|
-
text += "Required disclaimer: none\n";
|
|
3408
|
-
}
|
|
3409
|
-
return { content: [{ type: "text", text }] };
|
|
3410
|
-
}
|
|
3411
|
-
);
|
|
3412
|
-
}
|
|
3413
|
-
|
|
3414
|
-
// src/tools/audit-log.ts
|
|
3415
|
-
import { z as z13 } from "zod";
|
|
3416
|
-
function registerAuditLogTools(server2, client2) {
|
|
3417
|
-
server2.tool(
|
|
3418
|
-
"get_audit_log",
|
|
3419
|
-
"View the history of all publish, send, schedule, and delete actions taken on the customer's account. Use this to review what was published recently, check activity, and troubleshoot issues.",
|
|
3420
|
-
{
|
|
3421
|
-
limit: z13.number().optional().describe("Maximum number of entries to return (default 20, max 200)"),
|
|
3422
|
-
action: z13.string().optional().describe("Filter by action type prefix, e.g. 'post.published', 'newsletter.sent', 'post.' for all post actions"),
|
|
3423
|
-
resource_type: z13.string().optional().describe("Filter by resource type: post, newsletter, account, media, publishing_rules, brand_voice")
|
|
3424
|
-
},
|
|
3425
|
-
{
|
|
3426
|
-
title: "Get Audit Log",
|
|
3427
|
-
readOnlyHint: true,
|
|
3428
|
-
destructiveHint: false,
|
|
3429
|
-
idempotentHint: true,
|
|
3430
|
-
openWorldHint: false
|
|
3431
|
-
},
|
|
3432
|
-
async (args) => {
|
|
3433
|
-
const params = {};
|
|
3434
|
-
if (args.limit) params.limit = String(args.limit);
|
|
3435
|
-
if (args.action) params.action = args.action;
|
|
3436
|
-
if (args.resource_type) params.resource_type = args.resource_type;
|
|
3437
|
-
const data = await client2.getAuditLog(params);
|
|
3438
|
-
const entries = data?.entries ?? [];
|
|
3439
|
-
if (entries.length === 0) {
|
|
3440
|
-
return {
|
|
3441
|
-
content: [{ type: "text", text: "## Audit Log\n\nNo entries found." }]
|
|
3442
|
-
};
|
|
3443
|
-
}
|
|
3444
|
-
let text = `## Audit Log (${entries.length} of ${data?.total ?? entries.length} entries)
|
|
3445
|
-
|
|
3446
|
-
`;
|
|
3447
|
-
for (const entry of entries) {
|
|
3448
|
-
const date = entry.createdAt ? new Date(entry.createdAt).toLocaleString() : "unknown";
|
|
3449
|
-
const details = entry.details && Object.keys(entry.details).length > 0 ? ` \u2014 ${JSON.stringify(entry.details)}` : "";
|
|
3450
|
-
text += `- **${date}** | ${entry.action} | ${entry.resourceType}`;
|
|
3451
|
-
if (entry.resourceId) text += ` #${entry.resourceId}`;
|
|
3452
|
-
text += `${details}
|
|
3453
|
-
`;
|
|
3454
|
-
}
|
|
3455
|
-
return { content: [{ type: "text", text }] };
|
|
2801
|
+
const response = {
|
|
2802
|
+
rules: {
|
|
2803
|
+
social_default_action: rules.socialDefaultAction ?? "draft",
|
|
2804
|
+
newsletter_default_action: rules.newsletterDefaultAction ?? "draft",
|
|
2805
|
+
require_preview_before_publish: rules.requirePreviewBeforePublish ?? true,
|
|
2806
|
+
require_double_confirm_newsletter: rules.requireDoubleConfirmNewsletter ?? true,
|
|
2807
|
+
require_double_confirm_social: rules.requireDoubleConfirmSocial ?? false,
|
|
2808
|
+
allow_immediate_publish: rules.allowImmediatePublish ?? false,
|
|
2809
|
+
allow_immediate_send: rules.allowImmediateSend ?? false,
|
|
2810
|
+
max_posts_per_day: rules.maxPostsPerDay ?? null,
|
|
2811
|
+
blocked_words: rules.blockedWords ?? [],
|
|
2812
|
+
required_disclaimer: rules.requiredDisclaimer ?? null
|
|
2813
|
+
},
|
|
2814
|
+
workflow: WORKFLOW
|
|
2815
|
+
};
|
|
2816
|
+
return {
|
|
2817
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
2818
|
+
};
|
|
3456
2819
|
}
|
|
3457
2820
|
);
|
|
3458
2821
|
}
|
|
3459
2822
|
|
|
3460
2823
|
// src/tools/sources.ts
|
|
3461
|
-
import { z as
|
|
2824
|
+
import { z as z13 } from "zod";
|
|
3462
2825
|
var TYPE_LABELS = {
|
|
3463
2826
|
feed: "RSS Feed",
|
|
3464
2827
|
website: "Website",
|
|
@@ -3480,12 +2843,12 @@ var TYPE_HINTS = {
|
|
|
3480
2843
|
function registerSourceTools(server2, client2) {
|
|
3481
2844
|
server2.tool(
|
|
3482
2845
|
"get_sources",
|
|
3483
|
-
"Get
|
|
2846
|
+
"Get saved content sources (RSS feeds, websites, YouTube channels, search topics).",
|
|
3484
2847
|
{
|
|
3485
|
-
type:
|
|
2848
|
+
type: z13.string().optional().describe(
|
|
3486
2849
|
"Optional: filter by type (feed, website, youtube, search, podcast, reddit, social)"
|
|
3487
2850
|
),
|
|
3488
|
-
category:
|
|
2851
|
+
category: z13.string().optional().describe("Optional: filter by category")
|
|
3489
2852
|
},
|
|
3490
2853
|
{
|
|
3491
2854
|
title: "Get Content Sources",
|
|
@@ -3546,13 +2909,13 @@ function registerSourceTools(server2, client2) {
|
|
|
3546
2909
|
);
|
|
3547
2910
|
server2.tool(
|
|
3548
2911
|
"add_source",
|
|
3549
|
-
"Save a new content source to
|
|
2912
|
+
"Save a new content source to monitor.",
|
|
3550
2913
|
{
|
|
3551
|
-
name:
|
|
3552
|
-
url:
|
|
3553
|
-
"URL of the source
|
|
2914
|
+
name: z13.string().describe("Display name for the source"),
|
|
2915
|
+
url: z13.string().optional().describe(
|
|
2916
|
+
"URL of the source. Not needed for 'search' type."
|
|
3554
2917
|
),
|
|
3555
|
-
type:
|
|
2918
|
+
type: z13.enum([
|
|
3556
2919
|
"feed",
|
|
3557
2920
|
"website",
|
|
3558
2921
|
"youtube",
|
|
@@ -3561,12 +2924,12 @@ function registerSourceTools(server2, client2) {
|
|
|
3561
2924
|
"reddit",
|
|
3562
2925
|
"social"
|
|
3563
2926
|
]).describe("Type of source. Determines how to fetch content from it."),
|
|
3564
|
-
category:
|
|
3565
|
-
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration'
|
|
2927
|
+
category: z13.string().optional().describe(
|
|
2928
|
+
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration')"
|
|
3566
2929
|
),
|
|
3567
|
-
tags:
|
|
3568
|
-
notes:
|
|
3569
|
-
searchQuery:
|
|
2930
|
+
tags: z13.array(z13.string()).optional().describe("Optional tags for filtering"),
|
|
2931
|
+
notes: z13.string().optional().describe("Optional notes about why this source matters"),
|
|
2932
|
+
searchQuery: z13.string().optional().describe(
|
|
3570
2933
|
"For 'search' type only: the keyword or topic to search for"
|
|
3571
2934
|
)
|
|
3572
2935
|
},
|
|
@@ -3606,12 +2969,12 @@ function registerSourceTools(server2, client2) {
|
|
|
3606
2969
|
);
|
|
3607
2970
|
server2.tool(
|
|
3608
2971
|
"update_source",
|
|
3609
|
-
"Update a saved content source.
|
|
2972
|
+
"Update a saved content source.",
|
|
3610
2973
|
{
|
|
3611
|
-
source_id:
|
|
3612
|
-
name:
|
|
3613
|
-
url:
|
|
3614
|
-
type:
|
|
2974
|
+
source_id: z13.number().describe("The ID of the source to update"),
|
|
2975
|
+
name: z13.string().optional().describe("New display name"),
|
|
2976
|
+
url: z13.string().optional().describe("New URL"),
|
|
2977
|
+
type: z13.enum([
|
|
3615
2978
|
"feed",
|
|
3616
2979
|
"website",
|
|
3617
2980
|
"youtube",
|
|
@@ -3620,11 +2983,11 @@ function registerSourceTools(server2, client2) {
|
|
|
3620
2983
|
"reddit",
|
|
3621
2984
|
"social"
|
|
3622
2985
|
]).optional().describe("New source type"),
|
|
3623
|
-
category:
|
|
3624
|
-
tags:
|
|
3625
|
-
notes:
|
|
3626
|
-
searchQuery:
|
|
3627
|
-
isActive:
|
|
2986
|
+
category: z13.string().optional().describe("New category"),
|
|
2987
|
+
tags: z13.array(z13.string()).optional().describe("New tags"),
|
|
2988
|
+
notes: z13.string().optional().describe("New notes"),
|
|
2989
|
+
searchQuery: z13.string().optional().describe("New search query"),
|
|
2990
|
+
isActive: z13.boolean().optional().describe("Set to false to deactivate")
|
|
3628
2991
|
},
|
|
3629
2992
|
{
|
|
3630
2993
|
title: "Update Content Source",
|
|
@@ -3652,15 +3015,10 @@ function registerSourceTools(server2, client2) {
|
|
|
3652
3015
|
);
|
|
3653
3016
|
server2.tool(
|
|
3654
3017
|
"remove_source",
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
REQUIRED WORKFLOW:
|
|
3658
|
-
1. ALWAYS set confirmed=false first to preview which source will be deleted
|
|
3659
|
-
2. Show the user the source details before confirming
|
|
3660
|
-
3. Only confirm after explicit user approval`,
|
|
3018
|
+
"Remove a content source. Set confirmed=false to preview first.",
|
|
3661
3019
|
{
|
|
3662
|
-
source_id:
|
|
3663
|
-
confirmed:
|
|
3020
|
+
source_id: z13.number().describe("The ID of the source to remove"),
|
|
3021
|
+
confirmed: z13.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
3664
3022
|
},
|
|
3665
3023
|
{
|
|
3666
3024
|
title: "Remove Content Source",
|
|
@@ -3692,13 +3050,13 @@ This will permanently remove this content source. Call this tool again with conf
|
|
|
3692
3050
|
}
|
|
3693
3051
|
|
|
3694
3052
|
// src/tools/newsletter-advanced.ts
|
|
3695
|
-
import { z as
|
|
3053
|
+
import { z as z14 } from "zod";
|
|
3696
3054
|
function registerNewsletterAdvancedTools(server2, client2) {
|
|
3697
3055
|
server2.tool(
|
|
3698
3056
|
"get_subscriber_by_email",
|
|
3699
|
-
"Look up a subscriber by
|
|
3057
|
+
"Look up a subscriber by email address.",
|
|
3700
3058
|
{
|
|
3701
|
-
email:
|
|
3059
|
+
email: z14.string().describe("Email address to look up")
|
|
3702
3060
|
},
|
|
3703
3061
|
{
|
|
3704
3062
|
title: "Get Subscriber by Email",
|
|
@@ -3716,9 +3074,9 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3716
3074
|
);
|
|
3717
3075
|
server2.tool(
|
|
3718
3076
|
"list_automations",
|
|
3719
|
-
"List all email automations
|
|
3077
|
+
"List all email automations and sequences.",
|
|
3720
3078
|
{
|
|
3721
|
-
page:
|
|
3079
|
+
page: z14.string().optional().describe("Page number for pagination")
|
|
3722
3080
|
},
|
|
3723
3081
|
{
|
|
3724
3082
|
title: "List Automations",
|
|
@@ -3738,12 +3096,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3738
3096
|
);
|
|
3739
3097
|
server2.tool(
|
|
3740
3098
|
"list_segments",
|
|
3741
|
-
"List subscriber segments.
|
|
3099
|
+
"List subscriber segments.",
|
|
3742
3100
|
{
|
|
3743
|
-
page:
|
|
3744
|
-
type:
|
|
3745
|
-
status:
|
|
3746
|
-
expand:
|
|
3101
|
+
page: z14.string().optional().describe("Page number"),
|
|
3102
|
+
type: z14.string().optional().describe("Filter by segment type"),
|
|
3103
|
+
status: z14.string().optional().describe("Filter by segment status"),
|
|
3104
|
+
expand: z14.string().optional().describe("Comma-separated expand fields (e.g. 'stats')")
|
|
3747
3105
|
},
|
|
3748
3106
|
{
|
|
3749
3107
|
title: "List Segments",
|
|
@@ -3766,10 +3124,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3766
3124
|
);
|
|
3767
3125
|
server2.tool(
|
|
3768
3126
|
"get_segment",
|
|
3769
|
-
"Get details
|
|
3127
|
+
"Get segment details. Set include_members=true to list members.",
|
|
3770
3128
|
{
|
|
3771
|
-
segment_id:
|
|
3772
|
-
expand:
|
|
3129
|
+
segment_id: z14.string().describe("The segment ID"),
|
|
3130
|
+
expand: z14.string().optional().describe("Comma-separated expand fields"),
|
|
3131
|
+
include_members: z14.boolean().optional().describe("Set to true to include the member list"),
|
|
3132
|
+
members_page: z14.string().optional().describe("Page number for members pagination")
|
|
3773
3133
|
},
|
|
3774
3134
|
{
|
|
3775
3135
|
title: "Get Segment",
|
|
@@ -3781,38 +3141,31 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3781
3141
|
async (args) => {
|
|
3782
3142
|
const params = {};
|
|
3783
3143
|
if (args.expand) params.expand = args.expand;
|
|
3784
|
-
const
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
title: "Get Segment Members",
|
|
3799
|
-
readOnlyHint: true,
|
|
3800
|
-
destructiveHint: false,
|
|
3801
|
-
idempotentHint: true,
|
|
3802
|
-
openWorldHint: true
|
|
3803
|
-
},
|
|
3804
|
-
async (args) => {
|
|
3805
|
-
const params = {};
|
|
3806
|
-
if (args.page) params.page = args.page;
|
|
3807
|
-
const result = await client2.getSegmentMembers(args.segment_id, params);
|
|
3144
|
+
const segment = await client2.getSegment(args.segment_id, params);
|
|
3145
|
+
if (!args.include_members) {
|
|
3146
|
+
return {
|
|
3147
|
+
content: [{ type: "text", text: JSON.stringify(segment, null, 2) }]
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
const memberParams = {};
|
|
3151
|
+
if (args.members_page) memberParams.page = args.members_page;
|
|
3152
|
+
const members = await client2.request(
|
|
3153
|
+
"GET",
|
|
3154
|
+
`/api/v1/newsletters/segments/${args.segment_id}/members`,
|
|
3155
|
+
void 0,
|
|
3156
|
+
memberParams
|
|
3157
|
+
);
|
|
3808
3158
|
return {
|
|
3809
|
-
content: [{
|
|
3159
|
+
content: [{
|
|
3160
|
+
type: "text",
|
|
3161
|
+
text: JSON.stringify({ segment, members }, null, 2)
|
|
3162
|
+
}]
|
|
3810
3163
|
};
|
|
3811
3164
|
}
|
|
3812
3165
|
);
|
|
3813
3166
|
server2.tool(
|
|
3814
3167
|
"list_custom_fields",
|
|
3815
|
-
"List all custom subscriber fields defined in your ESP.
|
|
3168
|
+
"List all custom subscriber fields defined in your ESP.",
|
|
3816
3169
|
{},
|
|
3817
3170
|
{
|
|
3818
3171
|
title: "List Custom Fields",
|
|
@@ -3828,42 +3181,6 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3828
3181
|
};
|
|
3829
3182
|
}
|
|
3830
3183
|
);
|
|
3831
|
-
server2.tool(
|
|
3832
|
-
"get_referral_program",
|
|
3833
|
-
"Get your newsletter's referral program details including milestones and rewards (Beehiiv only).",
|
|
3834
|
-
{},
|
|
3835
|
-
{
|
|
3836
|
-
title: "Get Referral Program",
|
|
3837
|
-
readOnlyHint: true,
|
|
3838
|
-
destructiveHint: false,
|
|
3839
|
-
idempotentHint: true,
|
|
3840
|
-
openWorldHint: true
|
|
3841
|
-
},
|
|
3842
|
-
async () => {
|
|
3843
|
-
const result = await client2.getReferralProgram();
|
|
3844
|
-
return {
|
|
3845
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3846
|
-
};
|
|
3847
|
-
}
|
|
3848
|
-
);
|
|
3849
|
-
server2.tool(
|
|
3850
|
-
"list_tiers",
|
|
3851
|
-
"List all subscription tiers (free and premium) from your ESP (Beehiiv only).",
|
|
3852
|
-
{},
|
|
3853
|
-
{
|
|
3854
|
-
title: "List Tiers",
|
|
3855
|
-
readOnlyHint: true,
|
|
3856
|
-
destructiveHint: false,
|
|
3857
|
-
idempotentHint: true,
|
|
3858
|
-
openWorldHint: true
|
|
3859
|
-
},
|
|
3860
|
-
async () => {
|
|
3861
|
-
const result = await client2.listEspTiers();
|
|
3862
|
-
return {
|
|
3863
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3864
|
-
};
|
|
3865
|
-
}
|
|
3866
|
-
);
|
|
3867
3184
|
server2.tool(
|
|
3868
3185
|
"list_post_templates",
|
|
3869
3186
|
"List all post/newsletter templates available in your ESP (Beehiiv only).",
|
|
@@ -3884,7 +3201,7 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3884
3201
|
);
|
|
3885
3202
|
server2.tool(
|
|
3886
3203
|
"get_post_aggregate_stats",
|
|
3887
|
-
"Get aggregate
|
|
3204
|
+
"Get aggregate stats across all posts and newsletters.",
|
|
3888
3205
|
{},
|
|
3889
3206
|
{
|
|
3890
3207
|
title: "Get Post Aggregate Stats",
|
|
@@ -3900,30 +3217,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3900
3217
|
};
|
|
3901
3218
|
}
|
|
3902
3219
|
);
|
|
3903
|
-
server2.tool(
|
|
3904
|
-
"list_esp_webhooks",
|
|
3905
|
-
"List all webhooks configured in your ESP for event notifications.",
|
|
3906
|
-
{},
|
|
3907
|
-
{
|
|
3908
|
-
title: "List ESP Webhooks",
|
|
3909
|
-
readOnlyHint: true,
|
|
3910
|
-
destructiveHint: false,
|
|
3911
|
-
idempotentHint: true,
|
|
3912
|
-
openWorldHint: true
|
|
3913
|
-
},
|
|
3914
|
-
async () => {
|
|
3915
|
-
const result = await client2.listEspWebhooks();
|
|
3916
|
-
return {
|
|
3917
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3918
|
-
};
|
|
3919
|
-
}
|
|
3920
|
-
);
|
|
3921
3220
|
server2.tool(
|
|
3922
3221
|
"tag_subscriber",
|
|
3923
|
-
"Add tags to a subscriber.
|
|
3222
|
+
"Add tags to a subscriber.",
|
|
3924
3223
|
{
|
|
3925
|
-
subscriber_id:
|
|
3926
|
-
tags:
|
|
3224
|
+
subscriber_id: z14.string().describe("The subscriber/subscription ID"),
|
|
3225
|
+
tags: z14.array(z14.string()).describe("Tags to add to the subscriber")
|
|
3927
3226
|
},
|
|
3928
3227
|
{
|
|
3929
3228
|
title: "Tag Subscriber",
|
|
@@ -3941,17 +3240,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3941
3240
|
);
|
|
3942
3241
|
server2.tool(
|
|
3943
3242
|
"update_subscriber",
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
REQUIRED WORKFLOW:
|
|
3947
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3948
|
-
2. Show the user what will happen before confirming
|
|
3949
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3243
|
+
"Update subscriber details or custom fields. Set confirmed=false to preview first.",
|
|
3950
3244
|
{
|
|
3951
|
-
subscriber_id:
|
|
3952
|
-
tier:
|
|
3953
|
-
custom_fields:
|
|
3954
|
-
confirmed:
|
|
3245
|
+
subscriber_id: z14.string().describe("The subscriber/subscription ID"),
|
|
3246
|
+
tier: z14.string().optional().describe("New tier for the subscriber"),
|
|
3247
|
+
custom_fields: z14.record(z14.unknown()).optional().describe("Custom field values to set"),
|
|
3248
|
+
confirmed: z14.boolean().default(false).describe("Set to true to confirm update")
|
|
3955
3249
|
},
|
|
3956
3250
|
{
|
|
3957
3251
|
title: "Update Subscriber",
|
|
@@ -3987,315 +3281,31 @@ Call again with confirmed=true to proceed.`
|
|
|
3987
3281
|
}
|
|
3988
3282
|
);
|
|
3989
3283
|
server2.tool(
|
|
3990
|
-
"
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
REQUIRED WORKFLOW:
|
|
3994
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3995
|
-
2. Show the user what will happen before confirming
|
|
3996
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3997
|
-
{
|
|
3998
|
-
name: z15.string().describe("Field name"),
|
|
3999
|
-
kind: z15.string().describe("Field type: string, integer, boolean, date, or enum"),
|
|
4000
|
-
options: z15.array(z15.string()).optional().describe("Options for enum-type fields"),
|
|
4001
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm creation")
|
|
4002
|
-
},
|
|
4003
|
-
{
|
|
4004
|
-
title: "Create Custom Field",
|
|
4005
|
-
readOnlyHint: false,
|
|
4006
|
-
destructiveHint: false,
|
|
4007
|
-
idempotentHint: false,
|
|
4008
|
-
openWorldHint: true
|
|
4009
|
-
},
|
|
4010
|
-
async (args) => {
|
|
4011
|
-
if (args.confirmed !== true) {
|
|
4012
|
-
const optionsLine = args.options ? `
|
|
4013
|
-
**Options:** ${args.options.join(", ")}` : "";
|
|
4014
|
-
return {
|
|
4015
|
-
content: [{
|
|
4016
|
-
type: "text",
|
|
4017
|
-
text: `## Create Custom Field
|
|
4018
|
-
|
|
4019
|
-
**Name:** ${args.name}
|
|
4020
|
-
**Kind:** ${args.kind}${optionsLine}
|
|
4021
|
-
|
|
4022
|
-
This creates a permanent schema-level field visible on all subscribers. Call again with confirmed=true to proceed.`
|
|
4023
|
-
}]
|
|
4024
|
-
};
|
|
4025
|
-
}
|
|
4026
|
-
const data = { name: args.name, kind: args.kind };
|
|
4027
|
-
if (args.options) data.options = args.options;
|
|
4028
|
-
const result = await client2.createCustomField(data);
|
|
4029
|
-
return {
|
|
4030
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
4031
|
-
};
|
|
4032
|
-
}
|
|
4033
|
-
);
|
|
4034
|
-
server2.tool(
|
|
4035
|
-
"update_custom_field",
|
|
4036
|
-
`Update an existing custom subscriber field's name or kind. Renaming or rekeying a field can break automations that reference it.
|
|
4037
|
-
|
|
4038
|
-
REQUIRED WORKFLOW:
|
|
4039
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4040
|
-
2. Show the user what will happen before confirming
|
|
4041
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3284
|
+
"delete_subscriber",
|
|
3285
|
+
"Remove a subscriber. Set confirmed=false to preview first.",
|
|
4042
3286
|
{
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
kind: z15.string().optional().describe("New field type"),
|
|
4046
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm update")
|
|
3287
|
+
subscriber_id: z14.string().describe("The subscriber ID to delete"),
|
|
3288
|
+
confirmed: z14.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4047
3289
|
},
|
|
4048
3290
|
{
|
|
4049
|
-
title: "
|
|
3291
|
+
title: "Delete Subscriber",
|
|
4050
3292
|
readOnlyHint: false,
|
|
4051
|
-
destructiveHint:
|
|
3293
|
+
destructiveHint: true,
|
|
4052
3294
|
idempotentHint: true,
|
|
4053
3295
|
openWorldHint: true
|
|
4054
3296
|
},
|
|
4055
3297
|
async (args) => {
|
|
4056
3298
|
if (args.confirmed !== true) {
|
|
4057
|
-
const changes = [];
|
|
4058
|
-
if (args.name) changes.push(`**Name:** ${args.name}`);
|
|
4059
|
-
if (args.kind) changes.push(`**Kind:** ${args.kind}`);
|
|
4060
3299
|
return {
|
|
4061
3300
|
content: [{
|
|
4062
3301
|
type: "text",
|
|
4063
|
-
text: `##
|
|
3302
|
+
text: `## Delete Subscriber
|
|
4064
3303
|
|
|
4065
|
-
**
|
|
4066
|
-
${changes.join("\n")}
|
|
3304
|
+
**Subscriber ID:** ${args.subscriber_id}
|
|
4067
3305
|
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
}
|
|
4072
|
-
const data = {};
|
|
4073
|
-
if (args.name) data.name = args.name;
|
|
4074
|
-
if (args.kind) data.kind = args.kind;
|
|
4075
|
-
const result = await client2.updateCustomField(args.field_id, data);
|
|
4076
|
-
return {
|
|
4077
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
4078
|
-
};
|
|
4079
|
-
}
|
|
4080
|
-
);
|
|
4081
|
-
server2.tool(
|
|
4082
|
-
"recalculate_segment",
|
|
4083
|
-
"Trigger a recalculation of a segment's membership. Useful after changing subscriber data or segment rules (Beehiiv only).",
|
|
4084
|
-
{
|
|
4085
|
-
segment_id: z15.string().describe("The segment ID to recalculate")
|
|
4086
|
-
},
|
|
4087
|
-
{
|
|
4088
|
-
title: "Recalculate Segment",
|
|
4089
|
-
readOnlyHint: false,
|
|
4090
|
-
destructiveHint: false,
|
|
4091
|
-
idempotentHint: true,
|
|
4092
|
-
openWorldHint: true
|
|
4093
|
-
},
|
|
4094
|
-
async (args) => {
|
|
4095
|
-
await client2.recalculateSegment(args.segment_id);
|
|
4096
|
-
return {
|
|
4097
|
-
content: [{ type: "text", text: "Segment recalculation triggered." }]
|
|
4098
|
-
};
|
|
4099
|
-
}
|
|
4100
|
-
);
|
|
4101
|
-
server2.tool(
|
|
4102
|
-
"enroll_in_automation",
|
|
4103
|
-
`Enroll a subscriber in an automation/journey. Provide either an email or subscription ID. On Kit, enrolls into a sequence.
|
|
4104
|
-
|
|
4105
|
-
REQUIRED WORKFLOW:
|
|
4106
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4107
|
-
2. Show the user what will happen before confirming
|
|
4108
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4109
|
-
{
|
|
4110
|
-
automation_id: z15.string().describe("The automation ID"),
|
|
4111
|
-
email: z15.string().optional().describe("Subscriber email to enroll"),
|
|
4112
|
-
subscription_id: z15.string().optional().describe("Subscription ID to enroll"),
|
|
4113
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm enrollment")
|
|
4114
|
-
},
|
|
4115
|
-
{
|
|
4116
|
-
title: "Enroll in Automation",
|
|
4117
|
-
readOnlyHint: false,
|
|
4118
|
-
destructiveHint: false,
|
|
4119
|
-
idempotentHint: false,
|
|
4120
|
-
openWorldHint: true
|
|
4121
|
-
},
|
|
4122
|
-
async (args) => {
|
|
4123
|
-
if (args.confirmed !== true) {
|
|
4124
|
-
const target = args.email ?? args.subscription_id ?? "unknown";
|
|
4125
|
-
return {
|
|
4126
|
-
content: [{
|
|
4127
|
-
type: "text",
|
|
4128
|
-
text: `## Enroll in Automation
|
|
4129
|
-
|
|
4130
|
-
**Automation ID:** ${args.automation_id}
|
|
4131
|
-
**Target:** ${target}
|
|
4132
|
-
|
|
4133
|
-
This will add the subscriber to the automation journey. Call again with confirmed=true to proceed.`
|
|
4134
|
-
}]
|
|
4135
|
-
};
|
|
4136
|
-
}
|
|
4137
|
-
const data = {};
|
|
4138
|
-
if (args.email) data.email = args.email;
|
|
4139
|
-
if (args.subscription_id) data.subscriptionId = args.subscription_id;
|
|
4140
|
-
await client2.enrollInAutomation(args.automation_id, data);
|
|
4141
|
-
return {
|
|
4142
|
-
content: [{ type: "text", text: "Subscriber enrolled in automation." }]
|
|
4143
|
-
};
|
|
4144
|
-
}
|
|
4145
|
-
);
|
|
4146
|
-
server2.tool(
|
|
4147
|
-
"bulk_add_subscribers",
|
|
4148
|
-
`Add multiple subscribers in a single request. Maximum 1000 subscribers per call.
|
|
4149
|
-
|
|
4150
|
-
REQUIRED WORKFLOW:
|
|
4151
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4152
|
-
2. Show the user what will happen before confirming
|
|
4153
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4154
|
-
{
|
|
4155
|
-
subscribers: z15.array(z15.object({
|
|
4156
|
-
email: z15.string().describe("Subscriber email"),
|
|
4157
|
-
data: z15.record(z15.unknown()).optional().describe("Additional subscriber data")
|
|
4158
|
-
})).describe("Array of subscribers to add"),
|
|
4159
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm bulk add")
|
|
4160
|
-
},
|
|
4161
|
-
{
|
|
4162
|
-
title: "Bulk Add Subscribers",
|
|
4163
|
-
readOnlyHint: false,
|
|
4164
|
-
destructiveHint: false,
|
|
4165
|
-
idempotentHint: false,
|
|
4166
|
-
openWorldHint: true
|
|
4167
|
-
},
|
|
4168
|
-
async (args) => {
|
|
4169
|
-
if (args.confirmed !== true) {
|
|
4170
|
-
return {
|
|
4171
|
-
content: [{
|
|
4172
|
-
type: "text",
|
|
4173
|
-
text: `## Bulk Add Subscribers
|
|
4174
|
-
|
|
4175
|
-
**Count:** ${args.subscribers.length} subscriber(s)
|
|
4176
|
-
**Sample:** ${args.subscribers.slice(0, 3).map((s) => s.email).join(", ")}${args.subscribers.length > 3 ? "..." : ""}
|
|
4177
|
-
|
|
4178
|
-
Call again with confirmed=true to proceed.`
|
|
4179
|
-
}]
|
|
4180
|
-
};
|
|
4181
|
-
}
|
|
4182
|
-
const result = await client2.bulkCreateSubscribers({ subscribers: args.subscribers });
|
|
4183
|
-
return {
|
|
4184
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
4185
|
-
};
|
|
4186
|
-
}
|
|
4187
|
-
);
|
|
4188
|
-
server2.tool(
|
|
4189
|
-
"create_esp_webhook",
|
|
4190
|
-
`Create a webhook in your ESP to receive event notifications. Note: Kit only supports one event per webhook.
|
|
4191
|
-
|
|
4192
|
-
REQUIRED WORKFLOW:
|
|
4193
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4194
|
-
2. Show the user what will happen before confirming
|
|
4195
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4196
|
-
{
|
|
4197
|
-
url: z15.string().describe("Webhook URL to receive events"),
|
|
4198
|
-
event_types: z15.array(z15.string()).describe("Event types to subscribe to (e.g. 'subscription.created', 'post.sent')"),
|
|
4199
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm creation")
|
|
4200
|
-
},
|
|
4201
|
-
{
|
|
4202
|
-
title: "Create ESP Webhook",
|
|
4203
|
-
readOnlyHint: false,
|
|
4204
|
-
destructiveHint: false,
|
|
4205
|
-
idempotentHint: false,
|
|
4206
|
-
openWorldHint: true
|
|
4207
|
-
},
|
|
4208
|
-
async (args) => {
|
|
4209
|
-
if (args.confirmed !== true) {
|
|
4210
|
-
return {
|
|
4211
|
-
content: [{
|
|
4212
|
-
type: "text",
|
|
4213
|
-
text: `## Create ESP Webhook
|
|
4214
|
-
|
|
4215
|
-
**URL:** ${args.url}
|
|
4216
|
-
**Events:** ${args.event_types.join(", ")}
|
|
4217
|
-
|
|
4218
|
-
Call again with confirmed=true to create.`
|
|
4219
|
-
}]
|
|
4220
|
-
};
|
|
4221
|
-
}
|
|
4222
|
-
const result = await client2.createEspWebhook({ url: args.url, eventTypes: args.event_types });
|
|
4223
|
-
return {
|
|
4224
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
4225
|
-
};
|
|
4226
|
-
}
|
|
4227
|
-
);
|
|
4228
|
-
server2.tool(
|
|
4229
|
-
"delete_broadcast",
|
|
4230
|
-
`Permanently delete a newsletter/broadcast. This action is permanent and cannot be undone.
|
|
4231
|
-
|
|
4232
|
-
REQUIRED WORKFLOW:
|
|
4233
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4234
|
-
2. Show the user what will happen before confirming
|
|
4235
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4236
|
-
{
|
|
4237
|
-
broadcast_id: z15.string().describe("The broadcast ID to delete"),
|
|
4238
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4239
|
-
},
|
|
4240
|
-
{
|
|
4241
|
-
title: "Delete Broadcast",
|
|
4242
|
-
readOnlyHint: false,
|
|
4243
|
-
destructiveHint: true,
|
|
4244
|
-
idempotentHint: true,
|
|
4245
|
-
openWorldHint: true
|
|
4246
|
-
},
|
|
4247
|
-
async (args) => {
|
|
4248
|
-
if (args.confirmed !== true) {
|
|
4249
|
-
return {
|
|
4250
|
-
content: [{
|
|
4251
|
-
type: "text",
|
|
4252
|
-
text: `## Delete Broadcast
|
|
4253
|
-
|
|
4254
|
-
**Broadcast ID:** ${args.broadcast_id}
|
|
4255
|
-
|
|
4256
|
-
**This action is permanent and cannot be undone.**
|
|
4257
|
-
|
|
4258
|
-
Call again with confirmed=true to delete.`
|
|
4259
|
-
}]
|
|
4260
|
-
};
|
|
4261
|
-
}
|
|
4262
|
-
await client2.deleteBroadcast(args.broadcast_id);
|
|
4263
|
-
return {
|
|
4264
|
-
content: [{ type: "text", text: "Broadcast deleted." }]
|
|
4265
|
-
};
|
|
4266
|
-
}
|
|
4267
|
-
);
|
|
4268
|
-
server2.tool(
|
|
4269
|
-
"delete_subscriber",
|
|
4270
|
-
`Remove a subscriber from your mailing list. On Beehiiv this permanently deletes the subscriber. On Kit this unsubscribes them (Kit has no hard delete).
|
|
4271
|
-
|
|
4272
|
-
REQUIRED WORKFLOW:
|
|
4273
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4274
|
-
2. Show the user what will happen before confirming
|
|
4275
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4276
|
-
{
|
|
4277
|
-
subscriber_id: z15.string().describe("The subscriber ID to delete"),
|
|
4278
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4279
|
-
},
|
|
4280
|
-
{
|
|
4281
|
-
title: "Delete Subscriber",
|
|
4282
|
-
readOnlyHint: false,
|
|
4283
|
-
destructiveHint: true,
|
|
4284
|
-
idempotentHint: true,
|
|
4285
|
-
openWorldHint: true
|
|
4286
|
-
},
|
|
4287
|
-
async (args) => {
|
|
4288
|
-
if (args.confirmed !== true) {
|
|
4289
|
-
return {
|
|
4290
|
-
content: [{
|
|
4291
|
-
type: "text",
|
|
4292
|
-
text: `## Delete Subscriber
|
|
4293
|
-
|
|
4294
|
-
**Subscriber ID:** ${args.subscriber_id}
|
|
4295
|
-
|
|
4296
|
-
**This action is permanent and cannot be undone.** The subscriber will be removed from all segments and automations.
|
|
4297
|
-
|
|
4298
|
-
Call again with confirmed=true to delete.`
|
|
3306
|
+
**This action is permanent and cannot be undone.** The subscriber will be removed from all segments and automations.
|
|
3307
|
+
|
|
3308
|
+
Call again with confirmed=true to delete.`
|
|
4299
3309
|
}]
|
|
4300
3310
|
};
|
|
4301
3311
|
}
|
|
@@ -4305,1577 +3315,6 @@ Call again with confirmed=true to delete.`
|
|
|
4305
3315
|
};
|
|
4306
3316
|
}
|
|
4307
3317
|
);
|
|
4308
|
-
server2.tool(
|
|
4309
|
-
"delete_segment",
|
|
4310
|
-
`Permanently delete a subscriber segment. This action is permanent and cannot be undone (Beehiiv only).
|
|
4311
|
-
|
|
4312
|
-
REQUIRED WORKFLOW:
|
|
4313
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4314
|
-
2. Show the user what will happen before confirming
|
|
4315
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4316
|
-
{
|
|
4317
|
-
segment_id: z15.string().describe("The segment ID to delete"),
|
|
4318
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4319
|
-
},
|
|
4320
|
-
{
|
|
4321
|
-
title: "Delete Segment",
|
|
4322
|
-
readOnlyHint: false,
|
|
4323
|
-
destructiveHint: true,
|
|
4324
|
-
idempotentHint: true,
|
|
4325
|
-
openWorldHint: true
|
|
4326
|
-
},
|
|
4327
|
-
async (args) => {
|
|
4328
|
-
if (args.confirmed !== true) {
|
|
4329
|
-
return {
|
|
4330
|
-
content: [{
|
|
4331
|
-
type: "text",
|
|
4332
|
-
text: `## Delete Segment
|
|
4333
|
-
|
|
4334
|
-
**Segment ID:** ${args.segment_id}
|
|
4335
|
-
|
|
4336
|
-
**This action is permanent and cannot be undone.** Subscribers in this segment will not be deleted, but the segment grouping will be removed.
|
|
4337
|
-
|
|
4338
|
-
Call again with confirmed=true to delete.`
|
|
4339
|
-
}]
|
|
4340
|
-
};
|
|
4341
|
-
}
|
|
4342
|
-
await client2.deleteSegment(args.segment_id);
|
|
4343
|
-
return {
|
|
4344
|
-
content: [{ type: "text", text: "Segment deleted." }]
|
|
4345
|
-
};
|
|
4346
|
-
}
|
|
4347
|
-
);
|
|
4348
|
-
server2.tool(
|
|
4349
|
-
"delete_custom_field",
|
|
4350
|
-
`Permanently delete a custom subscriber field and remove it from all subscribers. This action is permanent and cannot be undone.
|
|
4351
|
-
|
|
4352
|
-
REQUIRED WORKFLOW:
|
|
4353
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4354
|
-
2. Show the user what will happen before confirming
|
|
4355
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4356
|
-
{
|
|
4357
|
-
field_id: z15.string().describe("The custom field ID to delete"),
|
|
4358
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4359
|
-
},
|
|
4360
|
-
{
|
|
4361
|
-
title: "Delete Custom Field",
|
|
4362
|
-
readOnlyHint: false,
|
|
4363
|
-
destructiveHint: true,
|
|
4364
|
-
idempotentHint: true,
|
|
4365
|
-
openWorldHint: true
|
|
4366
|
-
},
|
|
4367
|
-
async (args) => {
|
|
4368
|
-
if (args.confirmed !== true) {
|
|
4369
|
-
return {
|
|
4370
|
-
content: [{
|
|
4371
|
-
type: "text",
|
|
4372
|
-
text: `## Delete Custom Field
|
|
4373
|
-
|
|
4374
|
-
**Field ID:** ${args.field_id}
|
|
4375
|
-
|
|
4376
|
-
**This action is permanent and cannot be undone.** The field and its data will be removed from all subscribers.
|
|
4377
|
-
|
|
4378
|
-
Call again with confirmed=true to delete.`
|
|
4379
|
-
}]
|
|
4380
|
-
};
|
|
4381
|
-
}
|
|
4382
|
-
await client2.deleteCustomField(args.field_id);
|
|
4383
|
-
return {
|
|
4384
|
-
content: [{ type: "text", text: "Custom field deleted." }]
|
|
4385
|
-
};
|
|
4386
|
-
}
|
|
4387
|
-
);
|
|
4388
|
-
server2.tool(
|
|
4389
|
-
"delete_esp_webhook",
|
|
4390
|
-
`Permanently delete a webhook from your ESP. This action is permanent and cannot be undone.
|
|
4391
|
-
|
|
4392
|
-
REQUIRED WORKFLOW:
|
|
4393
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4394
|
-
2. Show the user what will happen before confirming
|
|
4395
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4396
|
-
{
|
|
4397
|
-
webhook_id: z15.string().describe("The webhook ID to delete"),
|
|
4398
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4399
|
-
},
|
|
4400
|
-
{
|
|
4401
|
-
title: "Delete ESP Webhook",
|
|
4402
|
-
readOnlyHint: false,
|
|
4403
|
-
destructiveHint: true,
|
|
4404
|
-
idempotentHint: true,
|
|
4405
|
-
openWorldHint: true
|
|
4406
|
-
},
|
|
4407
|
-
async (args) => {
|
|
4408
|
-
if (args.confirmed !== true) {
|
|
4409
|
-
return {
|
|
4410
|
-
content: [{
|
|
4411
|
-
type: "text",
|
|
4412
|
-
text: `## Delete ESP Webhook
|
|
4413
|
-
|
|
4414
|
-
**Webhook ID:** ${args.webhook_id}
|
|
4415
|
-
|
|
4416
|
-
**This action is permanent and cannot be undone.** You will stop receiving event notifications at this webhook URL.
|
|
4417
|
-
|
|
4418
|
-
Call again with confirmed=true to delete.`
|
|
4419
|
-
}]
|
|
4420
|
-
};
|
|
4421
|
-
}
|
|
4422
|
-
await client2.deleteEspWebhook(args.webhook_id);
|
|
4423
|
-
return {
|
|
4424
|
-
content: [{ type: "text", text: "Webhook deleted." }]
|
|
4425
|
-
};
|
|
4426
|
-
}
|
|
4427
|
-
);
|
|
4428
|
-
}
|
|
4429
|
-
|
|
4430
|
-
// src/tools/carousel.ts
|
|
4431
|
-
import { z as z16 } from "zod";
|
|
4432
|
-
function registerCarouselTools(server2, client2) {
|
|
4433
|
-
server2.tool(
|
|
4434
|
-
"get_carousel_template",
|
|
4435
|
-
"Get the customer's carousel template configuration. Returns brand colors, logo, CTA text, and other visual settings used to generate carousels from article URLs.",
|
|
4436
|
-
{},
|
|
4437
|
-
{
|
|
4438
|
-
title: "Get Carousel Template",
|
|
4439
|
-
readOnlyHint: true,
|
|
4440
|
-
destructiveHint: false,
|
|
4441
|
-
idempotentHint: true,
|
|
4442
|
-
openWorldHint: false
|
|
4443
|
-
},
|
|
4444
|
-
async () => {
|
|
4445
|
-
try {
|
|
4446
|
-
const template = await client2.getCarouselTemplate();
|
|
4447
|
-
const lines = [];
|
|
4448
|
-
lines.push(`## Carousel Template: ${template.name || "Not configured"}`);
|
|
4449
|
-
lines.push("");
|
|
4450
|
-
if (!template.id) {
|
|
4451
|
-
lines.push("No carousel template has been configured yet.");
|
|
4452
|
-
lines.push("");
|
|
4453
|
-
lines.push("Use `save_carousel_template` to set one up with your brand name, colors, and CTA text.");
|
|
4454
|
-
return {
|
|
4455
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4456
|
-
};
|
|
4457
|
-
}
|
|
4458
|
-
lines.push(`**Brand Name:** ${template.brandName || "\u2014"}`);
|
|
4459
|
-
if (template.logoUrl) lines.push(`**Logo:** ${template.logoUrl}`);
|
|
4460
|
-
lines.push(`**Primary Color:** ${template.colorPrimary || "#1a1a2e"}`);
|
|
4461
|
-
lines.push(`**Secondary Color:** ${template.colorSecondary || "#e94560"}`);
|
|
4462
|
-
lines.push(`**Accent Color:** ${template.colorAccent || "#ffffff"}`);
|
|
4463
|
-
lines.push(`**CTA Text:** ${template.ctaText || "Read the full story \u2192"}`);
|
|
4464
|
-
lines.push(`**Logo Placement:** ${template.logoPlacement || "top-left"}`);
|
|
4465
|
-
lines.push("");
|
|
4466
|
-
return {
|
|
4467
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4468
|
-
};
|
|
4469
|
-
} catch (error) {
|
|
4470
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4471
|
-
if (message.includes("404")) {
|
|
4472
|
-
return {
|
|
4473
|
-
content: [
|
|
4474
|
-
{
|
|
4475
|
-
type: "text",
|
|
4476
|
-
text: "No carousel template has been configured yet. Use `save_carousel_template` to set one up."
|
|
4477
|
-
}
|
|
4478
|
-
]
|
|
4479
|
-
};
|
|
4480
|
-
}
|
|
4481
|
-
throw error;
|
|
4482
|
-
}
|
|
4483
|
-
}
|
|
4484
|
-
);
|
|
4485
|
-
server2.tool(
|
|
4486
|
-
"save_carousel_template",
|
|
4487
|
-
`Create or update the customer's carousel template. This configures the visual style for auto-generated carousels (brand colors, logo, CTA text).
|
|
4488
|
-
|
|
4489
|
-
USAGE:
|
|
4490
|
-
- Set confirmed=false first to preview, then confirmed=true after user approval
|
|
4491
|
-
- brand_name is required
|
|
4492
|
-
- All color values should be hex codes (e.g. "#1a1a2e")
|
|
4493
|
-
- logo_placement: "top-left", "top-right", or "top-center"`,
|
|
4494
|
-
{
|
|
4495
|
-
brand_name: z16.string().max(255).describe("Brand name displayed on CTA slide"),
|
|
4496
|
-
color_primary: z16.string().max(20).optional().describe('Primary background color (hex, e.g. "#1a1a2e")'),
|
|
4497
|
-
color_secondary: z16.string().max(20).optional().describe('Secondary/accent color for buttons and highlights (hex, e.g. "#e94560")'),
|
|
4498
|
-
color_accent: z16.string().max(20).optional().describe('Text color (hex, e.g. "#ffffff")'),
|
|
4499
|
-
cta_text: z16.string().max(255).optional().describe('Call-to-action text on the final slide (e.g. "Read the full story \u2192")'),
|
|
4500
|
-
logo_placement: z16.enum(["top-left", "top-right", "top-center"]).optional().describe("Where to place the logo on slides"),
|
|
4501
|
-
confirmed: z16.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
4502
|
-
},
|
|
4503
|
-
{
|
|
4504
|
-
title: "Save Carousel Template",
|
|
4505
|
-
readOnlyHint: false,
|
|
4506
|
-
destructiveHint: false,
|
|
4507
|
-
idempotentHint: false,
|
|
4508
|
-
openWorldHint: false
|
|
4509
|
-
},
|
|
4510
|
-
async (args) => {
|
|
4511
|
-
const payload = {
|
|
4512
|
-
brandName: args.brand_name
|
|
4513
|
-
};
|
|
4514
|
-
if (args.color_primary !== void 0) payload.colorPrimary = args.color_primary;
|
|
4515
|
-
if (args.color_secondary !== void 0) payload.colorSecondary = args.color_secondary;
|
|
4516
|
-
if (args.color_accent !== void 0) payload.colorAccent = args.color_accent;
|
|
4517
|
-
if (args.cta_text !== void 0) payload.ctaText = args.cta_text;
|
|
4518
|
-
if (args.logo_placement !== void 0) payload.logoPlacement = args.logo_placement;
|
|
4519
|
-
if (args.confirmed !== true) {
|
|
4520
|
-
const lines2 = [];
|
|
4521
|
-
lines2.push("## Carousel Template Preview");
|
|
4522
|
-
lines2.push("");
|
|
4523
|
-
lines2.push(`**Brand Name:** ${args.brand_name}`);
|
|
4524
|
-
if (args.color_primary) lines2.push(`**Primary Color:** ${args.color_primary}`);
|
|
4525
|
-
if (args.color_secondary) lines2.push(`**Secondary Color:** ${args.color_secondary}`);
|
|
4526
|
-
if (args.color_accent) lines2.push(`**Accent Color:** ${args.color_accent}`);
|
|
4527
|
-
if (args.cta_text) lines2.push(`**CTA Text:** ${args.cta_text}`);
|
|
4528
|
-
if (args.logo_placement) lines2.push(`**Logo Placement:** ${args.logo_placement}`);
|
|
4529
|
-
lines2.push("");
|
|
4530
|
-
lines2.push("---");
|
|
4531
|
-
lines2.push("Call this tool again with **confirmed=true** to save these settings.");
|
|
4532
|
-
return {
|
|
4533
|
-
content: [{ type: "text", text: lines2.join("\n") }]
|
|
4534
|
-
};
|
|
4535
|
-
}
|
|
4536
|
-
const result = await client2.updateCarouselTemplate(payload);
|
|
4537
|
-
const lines = [];
|
|
4538
|
-
lines.push(`Carousel template "${result.brandName || args.brand_name}" has been saved successfully.`);
|
|
4539
|
-
lines.push("");
|
|
4540
|
-
const updated = ["brand name"];
|
|
4541
|
-
if (args.color_primary) updated.push("primary color");
|
|
4542
|
-
if (args.color_secondary) updated.push("secondary color");
|
|
4543
|
-
if (args.color_accent) updated.push("accent color");
|
|
4544
|
-
if (args.cta_text) updated.push("CTA text");
|
|
4545
|
-
if (args.logo_placement) updated.push("logo placement");
|
|
4546
|
-
lines.push(`**Updated:** ${updated.join(", ")}`);
|
|
4547
|
-
return {
|
|
4548
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4549
|
-
};
|
|
4550
|
-
}
|
|
4551
|
-
);
|
|
4552
|
-
server2.tool(
|
|
4553
|
-
"generate_carousel",
|
|
4554
|
-
"Generate a full multi-slide carousel from an article URL. Extracts headline and key points using AI, renders branded slides (hero + key points + CTA), and uploads them to CDN. Returns an array of CDN URLs for the rendered slides. Requires a carousel template to be configured first via save_carousel_template.",
|
|
4555
|
-
{
|
|
4556
|
-
url: z16.string().url().describe("The article URL to generate a carousel from")
|
|
4557
|
-
},
|
|
4558
|
-
{
|
|
4559
|
-
title: "Generate Carousel",
|
|
4560
|
-
readOnlyHint: false,
|
|
4561
|
-
destructiveHint: false,
|
|
4562
|
-
idempotentHint: false,
|
|
4563
|
-
openWorldHint: true
|
|
4564
|
-
},
|
|
4565
|
-
async (args) => {
|
|
4566
|
-
try {
|
|
4567
|
-
const result = await client2.generateCarousel({
|
|
4568
|
-
url: args.url
|
|
4569
|
-
});
|
|
4570
|
-
const lines = [];
|
|
4571
|
-
lines.push("## Carousel Generated");
|
|
4572
|
-
lines.push("");
|
|
4573
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4574
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4575
|
-
lines.push(`**Slides:** ${result.slides.length}`);
|
|
4576
|
-
lines.push("");
|
|
4577
|
-
lines.push("### Key Points");
|
|
4578
|
-
for (const point of result.article.keyPoints) {
|
|
4579
|
-
lines.push(`- ${point}`);
|
|
4580
|
-
}
|
|
4581
|
-
lines.push("");
|
|
4582
|
-
lines.push("### Slide URLs");
|
|
4583
|
-
for (const slide of result.slides) {
|
|
4584
|
-
lines.push(`${slide.slideNumber}. [${slide.type}](${slide.cdnUrl})`);
|
|
4585
|
-
}
|
|
4586
|
-
lines.push("");
|
|
4587
|
-
lines.push("These URLs can be used as `media_urls` when creating a post.");
|
|
4588
|
-
return {
|
|
4589
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4590
|
-
};
|
|
4591
|
-
} catch (error) {
|
|
4592
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4593
|
-
return {
|
|
4594
|
-
content: [
|
|
4595
|
-
{
|
|
4596
|
-
type: "text",
|
|
4597
|
-
text: `Failed to generate carousel: ${message}`
|
|
4598
|
-
}
|
|
4599
|
-
],
|
|
4600
|
-
isError: true
|
|
4601
|
-
};
|
|
4602
|
-
}
|
|
4603
|
-
}
|
|
4604
|
-
);
|
|
4605
|
-
server2.tool(
|
|
4606
|
-
"preview_carousel",
|
|
4607
|
-
"Generate a quick preview of the carousel hero slide from an article URL. Useful for checking how the template looks before generating the full carousel. Requires a carousel template to be configured first.",
|
|
4608
|
-
{
|
|
4609
|
-
url: z16.string().url().describe("The article URL to preview")
|
|
4610
|
-
},
|
|
4611
|
-
{
|
|
4612
|
-
title: "Preview Carousel",
|
|
4613
|
-
readOnlyHint: true,
|
|
4614
|
-
destructiveHint: false,
|
|
4615
|
-
idempotentHint: false,
|
|
4616
|
-
openWorldHint: true
|
|
4617
|
-
},
|
|
4618
|
-
async (args) => {
|
|
4619
|
-
try {
|
|
4620
|
-
const result = await client2.previewCarousel({
|
|
4621
|
-
url: args.url
|
|
4622
|
-
});
|
|
4623
|
-
const lines = [];
|
|
4624
|
-
lines.push("## Carousel Preview");
|
|
4625
|
-
lines.push("");
|
|
4626
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4627
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4628
|
-
lines.push("");
|
|
4629
|
-
lines.push(`**Hero Slide:** ${result.cdnUrl}`);
|
|
4630
|
-
lines.push("");
|
|
4631
|
-
lines.push("Use `generate_carousel` to create the full carousel with all slides.");
|
|
4632
|
-
return {
|
|
4633
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4634
|
-
};
|
|
4635
|
-
} catch (error) {
|
|
4636
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4637
|
-
return {
|
|
4638
|
-
content: [
|
|
4639
|
-
{
|
|
4640
|
-
type: "text",
|
|
4641
|
-
text: `Failed to preview carousel: ${message}`
|
|
4642
|
-
}
|
|
4643
|
-
],
|
|
4644
|
-
isError: true
|
|
4645
|
-
};
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
4648
|
-
);
|
|
4649
|
-
}
|
|
4650
|
-
|
|
4651
|
-
// src/tools/queue.ts
|
|
4652
|
-
import { z as z17 } from "zod";
|
|
4653
|
-
var DAY_NAMES = [
|
|
4654
|
-
"sunday",
|
|
4655
|
-
"monday",
|
|
4656
|
-
"tuesday",
|
|
4657
|
-
"wednesday",
|
|
4658
|
-
"thursday",
|
|
4659
|
-
"friday",
|
|
4660
|
-
"saturday"
|
|
4661
|
-
];
|
|
4662
|
-
var DAY_ENUM = z17.enum([
|
|
4663
|
-
"monday",
|
|
4664
|
-
"tuesday",
|
|
4665
|
-
"wednesday",
|
|
4666
|
-
"thursday",
|
|
4667
|
-
"friday",
|
|
4668
|
-
"saturday",
|
|
4669
|
-
"sunday"
|
|
4670
|
-
]);
|
|
4671
|
-
function dayNameToNumber(name) {
|
|
4672
|
-
const idx = DAY_NAMES.indexOf(name.toLowerCase());
|
|
4673
|
-
return idx === -1 ? 0 : idx;
|
|
4674
|
-
}
|
|
4675
|
-
function dayNumberToName(num) {
|
|
4676
|
-
return (DAY_NAMES[num] ?? "sunday").charAt(0).toUpperCase() + (DAY_NAMES[num] ?? "sunday").slice(1);
|
|
4677
|
-
}
|
|
4678
|
-
function getSlotId(slot) {
|
|
4679
|
-
if (slot.id) return String(slot.id);
|
|
4680
|
-
if (slot._id) return String(slot._id);
|
|
4681
|
-
return `${slot.dayOfWeek}_${slot.time}`;
|
|
4682
|
-
}
|
|
4683
|
-
function formatSlot(slot) {
|
|
4684
|
-
const day = dayNumberToName(slot.dayOfWeek);
|
|
4685
|
-
const platforms = (slot.platforms ?? []).join(", ");
|
|
4686
|
-
return `${day} ${slot.time} [${platforms}]`;
|
|
4687
|
-
}
|
|
4688
|
-
function registerQueueTools(server2, client2) {
|
|
4689
|
-
server2.tool(
|
|
4690
|
-
"create_queue_slot",
|
|
4691
|
-
"Create a new recurring time slot in the posting queue. Each slot fires once per week at the specified day and time. Use get_queue first to see existing slots and avoid duplicates.",
|
|
4692
|
-
{
|
|
4693
|
-
day_of_week: DAY_ENUM.describe("Day of the week for this slot"),
|
|
4694
|
-
time: z17.string().regex(/^\d{2}:\d{2}$/, "Must be HH:MM format (24-hour)").describe("Time in HH:MM 24-hour format, e.g. '09:00' or '14:30'"),
|
|
4695
|
-
timezone: z17.string().default("UTC").describe("IANA timezone, e.g. 'America/New_York'. Defaults to UTC."),
|
|
4696
|
-
platforms: z17.array(z17.string()).min(1).describe('Platforms for this slot, e.g. ["twitter", "linkedin"]'),
|
|
4697
|
-
confirmed: z17.boolean().default(false).describe(
|
|
4698
|
-
"Set to true to confirm creation. If false, returns a preview."
|
|
4699
|
-
)
|
|
4700
|
-
},
|
|
4701
|
-
{
|
|
4702
|
-
title: "Create Queue Slot",
|
|
4703
|
-
readOnlyHint: false,
|
|
4704
|
-
destructiveHint: false,
|
|
4705
|
-
idempotentHint: false,
|
|
4706
|
-
openWorldHint: true
|
|
4707
|
-
},
|
|
4708
|
-
async (args) => {
|
|
4709
|
-
const dayNum = dayNameToNumber(args.day_of_week);
|
|
4710
|
-
if (args.confirmed !== true) {
|
|
4711
|
-
let text = "## Create Queue Slot Preview\n\n";
|
|
4712
|
-
text += `**Day:** ${dayNumberToName(dayNum)}
|
|
4713
|
-
`;
|
|
4714
|
-
text += `**Time:** ${args.time}
|
|
4715
|
-
`;
|
|
4716
|
-
text += `**Timezone:** ${args.timezone}
|
|
4717
|
-
`;
|
|
4718
|
-
text += `**Platforms:** ${args.platforms.join(", ")}
|
|
4719
|
-
|
|
4720
|
-
`;
|
|
4721
|
-
try {
|
|
4722
|
-
const data2 = await client2.getQueue();
|
|
4723
|
-
const slots = data2?.slots ?? [];
|
|
4724
|
-
if (slots.length > 0) {
|
|
4725
|
-
text += "### Existing slots\n";
|
|
4726
|
-
for (const slot of slots) {
|
|
4727
|
-
text += `- ${formatSlot(slot)}
|
|
4728
|
-
`;
|
|
4729
|
-
}
|
|
4730
|
-
text += "\n";
|
|
4731
|
-
}
|
|
4732
|
-
} catch {
|
|
4733
|
-
}
|
|
4734
|
-
text += "Call this tool again with confirmed=true to create the slot.";
|
|
4735
|
-
return { content: [{ type: "text", text }] };
|
|
4736
|
-
}
|
|
4737
|
-
const data = await client2.getQueue();
|
|
4738
|
-
const existingSlots = data?.slots ?? [];
|
|
4739
|
-
const duplicate = existingSlots.find(
|
|
4740
|
-
(s) => s.dayOfWeek === dayNum && s.time === args.time
|
|
4741
|
-
);
|
|
4742
|
-
if (duplicate) {
|
|
4743
|
-
return {
|
|
4744
|
-
content: [
|
|
4745
|
-
{
|
|
4746
|
-
type: "text",
|
|
4747
|
-
text: `A slot already exists for ${dayNumberToName(dayNum)} at ${args.time}. Use update_queue_slot to modify it.`
|
|
4748
|
-
}
|
|
4749
|
-
]
|
|
4750
|
-
};
|
|
4751
|
-
}
|
|
4752
|
-
const updatedSlots = [
|
|
4753
|
-
...existingSlots.map((s) => ({
|
|
4754
|
-
dayOfWeek: s.dayOfWeek,
|
|
4755
|
-
time: s.time,
|
|
4756
|
-
platforms: s.platforms
|
|
4757
|
-
})),
|
|
4758
|
-
{
|
|
4759
|
-
dayOfWeek: dayNum,
|
|
4760
|
-
time: args.time,
|
|
4761
|
-
platforms: args.platforms
|
|
4762
|
-
}
|
|
4763
|
-
];
|
|
4764
|
-
await client2.updateQueue({
|
|
4765
|
-
slots: updatedSlots,
|
|
4766
|
-
active: data?.active,
|
|
4767
|
-
timezone: args.timezone
|
|
4768
|
-
});
|
|
4769
|
-
return {
|
|
4770
|
-
content: [
|
|
4771
|
-
{
|
|
4772
|
-
type: "text",
|
|
4773
|
-
text: `Queue slot created: ${dayNumberToName(dayNum)} at ${args.time} for ${args.platforms.join(", ")}. The queue now has ${updatedSlots.length} slot(s).`
|
|
4774
|
-
}
|
|
4775
|
-
]
|
|
4776
|
-
};
|
|
4777
|
-
}
|
|
4778
|
-
);
|
|
4779
|
-
server2.tool(
|
|
4780
|
-
"update_queue_slot",
|
|
4781
|
-
"Update an existing queue slot. Use get_queue first to see slot IDs. Only the fields you provide will be changed; omitted fields keep their current values.",
|
|
4782
|
-
{
|
|
4783
|
-
slot_id: z17.string().describe(
|
|
4784
|
-
"ID of the slot to update (shown by get_queue). Can be the Late API id or a dayOfWeek_time key like '1_09:00'."
|
|
4785
|
-
),
|
|
4786
|
-
day_of_week: DAY_ENUM.optional().describe("New day of the week"),
|
|
4787
|
-
time: z17.string().regex(/^\d{2}:\d{2}$/, "Must be HH:MM format (24-hour)").optional().describe("New time in HH:MM 24-hour format"),
|
|
4788
|
-
timezone: z17.string().optional().describe("New IANA timezone for the queue"),
|
|
4789
|
-
platforms: z17.array(z17.string()).min(1).optional().describe("New platforms list"),
|
|
4790
|
-
confirmed: z17.boolean().default(false).describe(
|
|
4791
|
-
"Set to true to confirm the update. If false, returns a preview."
|
|
4792
|
-
)
|
|
4793
|
-
},
|
|
4794
|
-
{
|
|
4795
|
-
title: "Update Queue Slot",
|
|
4796
|
-
readOnlyHint: false,
|
|
4797
|
-
destructiveHint: false,
|
|
4798
|
-
idempotentHint: true,
|
|
4799
|
-
openWorldHint: true
|
|
4800
|
-
},
|
|
4801
|
-
async (args) => {
|
|
4802
|
-
const data = await client2.getQueue();
|
|
4803
|
-
const slots = data?.slots ?? [];
|
|
4804
|
-
const slotIndex = slots.findIndex(
|
|
4805
|
-
(s) => getSlotId(s) === args.slot_id
|
|
4806
|
-
);
|
|
4807
|
-
if (slotIndex === -1) {
|
|
4808
|
-
const available = slots.map((s) => `${getSlotId(s)} (${formatSlot(s)})`).join("\n- ");
|
|
4809
|
-
return {
|
|
4810
|
-
content: [
|
|
4811
|
-
{
|
|
4812
|
-
type: "text",
|
|
4813
|
-
text: `Slot "${args.slot_id}" not found.
|
|
4814
|
-
|
|
4815
|
-
Available slots:
|
|
4816
|
-
- ${available || "(none)"}`
|
|
4817
|
-
}
|
|
4818
|
-
]
|
|
4819
|
-
};
|
|
4820
|
-
}
|
|
4821
|
-
const current = slots[slotIndex];
|
|
4822
|
-
const newDay = args.day_of_week !== void 0 ? dayNameToNumber(args.day_of_week) : current.dayOfWeek;
|
|
4823
|
-
const newTime = args.time ?? current.time;
|
|
4824
|
-
const newPlatforms = args.platforms ?? current.platforms;
|
|
4825
|
-
if (args.confirmed !== true) {
|
|
4826
|
-
let text = "## Update Queue Slot Preview\n\n";
|
|
4827
|
-
text += `**Current:** ${formatSlot(current)}
|
|
4828
|
-
`;
|
|
4829
|
-
text += `**Updated:** ${dayNumberToName(newDay)} ${newTime} [${(newPlatforms ?? []).join(", ")}]
|
|
4830
|
-
`;
|
|
4831
|
-
if (args.timezone) text += `**Timezone:** ${args.timezone}
|
|
4832
|
-
`;
|
|
4833
|
-
text += "\nCall this tool again with confirmed=true to apply the update.";
|
|
4834
|
-
return { content: [{ type: "text", text }] };
|
|
4835
|
-
}
|
|
4836
|
-
const updatedSlots = slots.map((s, i) => {
|
|
4837
|
-
if (i === slotIndex) {
|
|
4838
|
-
return {
|
|
4839
|
-
dayOfWeek: newDay,
|
|
4840
|
-
time: newTime,
|
|
4841
|
-
platforms: newPlatforms
|
|
4842
|
-
};
|
|
4843
|
-
}
|
|
4844
|
-
return {
|
|
4845
|
-
dayOfWeek: s.dayOfWeek,
|
|
4846
|
-
time: s.time,
|
|
4847
|
-
platforms: s.platforms
|
|
4848
|
-
};
|
|
4849
|
-
});
|
|
4850
|
-
await client2.updateQueue({
|
|
4851
|
-
slots: updatedSlots,
|
|
4852
|
-
active: data?.active,
|
|
4853
|
-
...args.timezone ? { timezone: args.timezone } : {}
|
|
4854
|
-
});
|
|
4855
|
-
return {
|
|
4856
|
-
content: [
|
|
4857
|
-
{
|
|
4858
|
-
type: "text",
|
|
4859
|
-
text: `Slot updated: ${dayNumberToName(newDay)} at ${newTime} for ${(newPlatforms ?? []).join(", ")}.`
|
|
4860
|
-
}
|
|
4861
|
-
]
|
|
4862
|
-
};
|
|
4863
|
-
}
|
|
4864
|
-
);
|
|
4865
|
-
server2.tool(
|
|
4866
|
-
"delete_queue_slot",
|
|
4867
|
-
"Delete a queue slot by ID. Use get_queue first to see slot IDs. This removes the recurring time slot -- any posts already scheduled for this slot are not affected.",
|
|
4868
|
-
{
|
|
4869
|
-
slot_id: z17.string().describe(
|
|
4870
|
-
"ID of the slot to delete (shown by get_queue)."
|
|
4871
|
-
),
|
|
4872
|
-
confirmed: z17.boolean().default(false).describe(
|
|
4873
|
-
"Set to true to confirm deletion. If false, shows slot details for review."
|
|
4874
|
-
)
|
|
4875
|
-
},
|
|
4876
|
-
{
|
|
4877
|
-
title: "Delete Queue Slot",
|
|
4878
|
-
readOnlyHint: false,
|
|
4879
|
-
destructiveHint: true,
|
|
4880
|
-
idempotentHint: true,
|
|
4881
|
-
openWorldHint: true
|
|
4882
|
-
},
|
|
4883
|
-
async (args) => {
|
|
4884
|
-
const data = await client2.getQueue();
|
|
4885
|
-
const slots = data?.slots ?? [];
|
|
4886
|
-
const slotIndex = slots.findIndex(
|
|
4887
|
-
(s) => getSlotId(s) === args.slot_id
|
|
4888
|
-
);
|
|
4889
|
-
if (slotIndex === -1) {
|
|
4890
|
-
const available = slots.map((s) => `${getSlotId(s)} (${formatSlot(s)})`).join("\n- ");
|
|
4891
|
-
return {
|
|
4892
|
-
content: [
|
|
4893
|
-
{
|
|
4894
|
-
type: "text",
|
|
4895
|
-
text: `Slot "${args.slot_id}" not found.
|
|
4896
|
-
|
|
4897
|
-
Available slots:
|
|
4898
|
-
- ${available || "(none)"}`
|
|
4899
|
-
}
|
|
4900
|
-
]
|
|
4901
|
-
};
|
|
4902
|
-
}
|
|
4903
|
-
const target = slots[slotIndex];
|
|
4904
|
-
if (args.confirmed !== true) {
|
|
4905
|
-
let text = "## Delete Queue Slot\n\n";
|
|
4906
|
-
text += `**Slot:** ${formatSlot(target)}
|
|
4907
|
-
`;
|
|
4908
|
-
text += `**ID:** ${getSlotId(target)}
|
|
4909
|
-
|
|
4910
|
-
`;
|
|
4911
|
-
text += `This will remove this recurring time slot. Posts already scheduled are not affected.
|
|
4912
|
-
|
|
4913
|
-
`;
|
|
4914
|
-
text += `The queue will have ${slots.length - 1} slot(s) remaining.
|
|
4915
|
-
|
|
4916
|
-
`;
|
|
4917
|
-
text += "Call this tool again with confirmed=true to delete.";
|
|
4918
|
-
return { content: [{ type: "text", text }] };
|
|
4919
|
-
}
|
|
4920
|
-
const updatedSlots = slots.filter((_, i) => i !== slotIndex).map((s) => ({
|
|
4921
|
-
dayOfWeek: s.dayOfWeek,
|
|
4922
|
-
time: s.time,
|
|
4923
|
-
platforms: s.platforms
|
|
4924
|
-
}));
|
|
4925
|
-
await client2.updateQueue({
|
|
4926
|
-
slots: updatedSlots,
|
|
4927
|
-
active: data?.active
|
|
4928
|
-
});
|
|
4929
|
-
return {
|
|
4930
|
-
content: [
|
|
4931
|
-
{
|
|
4932
|
-
type: "text",
|
|
4933
|
-
text: `Slot deleted: ${formatSlot(target)}. The queue now has ${updatedSlots.length} slot(s).`
|
|
4934
|
-
}
|
|
4935
|
-
]
|
|
4936
|
-
};
|
|
4937
|
-
}
|
|
4938
|
-
);
|
|
4939
|
-
server2.tool(
|
|
4940
|
-
"toggle_queue",
|
|
4941
|
-
`Enable or disable the entire posting queue. When disabled, no posts will be auto-scheduled to queue slots. Existing scheduled posts are not affected.
|
|
4942
|
-
|
|
4943
|
-
If the user tries to schedule_to_queue while the queue is inactive, warn them and suggest enabling it with this tool first.`,
|
|
4944
|
-
{
|
|
4945
|
-
active: z17.boolean().describe("true to enable the queue, false to disable it"),
|
|
4946
|
-
confirmed: z17.boolean().default(false).describe(
|
|
4947
|
-
"Set to true to confirm the change. If false, returns a preview."
|
|
4948
|
-
)
|
|
4949
|
-
},
|
|
4950
|
-
{
|
|
4951
|
-
title: "Toggle Queue",
|
|
4952
|
-
readOnlyHint: false,
|
|
4953
|
-
destructiveHint: false,
|
|
4954
|
-
idempotentHint: true,
|
|
4955
|
-
openWorldHint: true
|
|
4956
|
-
},
|
|
4957
|
-
async (args) => {
|
|
4958
|
-
const data = await client2.getQueue();
|
|
4959
|
-
const currentActive = data?.active ?? false;
|
|
4960
|
-
const slots = data?.slots ?? [];
|
|
4961
|
-
if (currentActive === args.active) {
|
|
4962
|
-
return {
|
|
4963
|
-
content: [
|
|
4964
|
-
{
|
|
4965
|
-
type: "text",
|
|
4966
|
-
text: `The queue is already ${args.active ? "enabled" : "disabled"}. No change needed.`
|
|
4967
|
-
}
|
|
4968
|
-
]
|
|
4969
|
-
};
|
|
4970
|
-
}
|
|
4971
|
-
if (args.confirmed !== true) {
|
|
4972
|
-
let text = `## Toggle Queue
|
|
4973
|
-
|
|
4974
|
-
`;
|
|
4975
|
-
text += `**Current status:** ${currentActive ? "Enabled" : "Disabled"}
|
|
4976
|
-
`;
|
|
4977
|
-
text += `**New status:** ${args.active ? "Enabled" : "Disabled"}
|
|
4978
|
-
`;
|
|
4979
|
-
text += `**Slots configured:** ${slots.length}
|
|
4980
|
-
|
|
4981
|
-
`;
|
|
4982
|
-
if (args.active && slots.length === 0) {
|
|
4983
|
-
text += "**Warning:** Enabling the queue with no slots configured will have no effect. Create slots first with create_queue_slot.\n\n";
|
|
4984
|
-
}
|
|
4985
|
-
if (!args.active && slots.length > 0) {
|
|
4986
|
-
text += "**Note:** Disabling the queue will stop auto-scheduling. Existing scheduled posts will still go out.\n\n";
|
|
4987
|
-
}
|
|
4988
|
-
text += "Call this tool again with confirmed=true to apply.";
|
|
4989
|
-
return { content: [{ type: "text", text }] };
|
|
4990
|
-
}
|
|
4991
|
-
const updatedSlots = slots.map((s) => ({
|
|
4992
|
-
dayOfWeek: s.dayOfWeek,
|
|
4993
|
-
time: s.time,
|
|
4994
|
-
platforms: s.platforms
|
|
4995
|
-
}));
|
|
4996
|
-
await client2.updateQueue({
|
|
4997
|
-
slots: updatedSlots,
|
|
4998
|
-
active: args.active
|
|
4999
|
-
});
|
|
5000
|
-
return {
|
|
5001
|
-
content: [
|
|
5002
|
-
{
|
|
5003
|
-
type: "text",
|
|
5004
|
-
text: `Queue ${args.active ? "enabled" : "disabled"} successfully.${args.active && slots.length === 0 ? " Note: no slots are configured yet -- use create_queue_slot to add time slots." : ""}`
|
|
5005
|
-
}
|
|
5006
|
-
]
|
|
5007
|
-
};
|
|
5008
|
-
}
|
|
5009
|
-
);
|
|
5010
|
-
}
|
|
5011
|
-
|
|
5012
|
-
// src/tools/canva.ts
|
|
5013
|
-
import { z as z18 } from "zod";
|
|
5014
|
-
var CANVA_NOT_CONNECTED = "Canva is not connected. Visit BuzzPoster settings to connect your Canva account.";
|
|
5015
|
-
function isNotConnectedError(error) {
|
|
5016
|
-
if (error instanceof Error) {
|
|
5017
|
-
return error.message.includes("Canva is not connected") || error.message.includes("400");
|
|
5018
|
-
}
|
|
5019
|
-
return false;
|
|
5020
|
-
}
|
|
5021
|
-
function registerCanvaTools(server2, client2) {
|
|
5022
|
-
server2.tool(
|
|
5023
|
-
"canva_upload_asset",
|
|
5024
|
-
"Upload an image to Canva from a URL. Returns the Canva asset ID and thumbnail URL. Use this to import images into Canva for use in designs.",
|
|
5025
|
-
{
|
|
5026
|
-
image_url: z18.string().url().describe("The URL of the image to upload to Canva."),
|
|
5027
|
-
name: z18.string().min(1).max(255).describe("A name for the uploaded asset.")
|
|
5028
|
-
},
|
|
5029
|
-
{
|
|
5030
|
-
title: "Upload Asset to Canva",
|
|
5031
|
-
readOnlyHint: false,
|
|
5032
|
-
destructiveHint: false,
|
|
5033
|
-
idempotentHint: false,
|
|
5034
|
-
openWorldHint: true
|
|
5035
|
-
},
|
|
5036
|
-
async ({ image_url, name }) => {
|
|
5037
|
-
try {
|
|
5038
|
-
const result = await client2.canvaUploadAsset({
|
|
5039
|
-
imageUrl: image_url,
|
|
5040
|
-
name
|
|
5041
|
-
});
|
|
5042
|
-
const lines = [`Asset uploaded to Canva successfully.`];
|
|
5043
|
-
lines.push(`Asset ID: ${result.assetId}`);
|
|
5044
|
-
if (result.thumbnailUrl) {
|
|
5045
|
-
lines.push(`Thumbnail: ${result.thumbnailUrl}`);
|
|
5046
|
-
}
|
|
5047
|
-
return {
|
|
5048
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5049
|
-
};
|
|
5050
|
-
} catch (error) {
|
|
5051
|
-
if (isNotConnectedError(error)) {
|
|
5052
|
-
return {
|
|
5053
|
-
content: [{ type: "text", text: CANVA_NOT_CONNECTED }]
|
|
5054
|
-
};
|
|
5055
|
-
}
|
|
5056
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5057
|
-
throw new Error(`Failed to upload asset to Canva: ${message}`);
|
|
5058
|
-
}
|
|
5059
|
-
}
|
|
5060
|
-
);
|
|
5061
|
-
server2.tool(
|
|
5062
|
-
"canva_create_design",
|
|
5063
|
-
"Create a new Canva design. Returns the design ID and an edit URL the user can open to customize the design in Canva's editor.",
|
|
5064
|
-
{
|
|
5065
|
-
title: z18.string().min(1).max(255).describe("Title for the new design."),
|
|
5066
|
-
design_type: z18.enum([
|
|
5067
|
-
"instagram_post",
|
|
5068
|
-
"facebook_post",
|
|
5069
|
-
"presentation",
|
|
5070
|
-
"poster",
|
|
5071
|
-
"story"
|
|
5072
|
-
]).describe("The type of design to create, which determines dimensions."),
|
|
5073
|
-
asset_id: z18.string().optional().describe(
|
|
5074
|
-
"Optional Canva asset ID to include in the design."
|
|
5075
|
-
)
|
|
5076
|
-
},
|
|
5077
|
-
{
|
|
5078
|
-
title: "Create Canva Design",
|
|
5079
|
-
readOnlyHint: false,
|
|
5080
|
-
destructiveHint: false,
|
|
5081
|
-
idempotentHint: false,
|
|
5082
|
-
openWorldHint: true
|
|
5083
|
-
},
|
|
5084
|
-
async ({ title, design_type, asset_id }) => {
|
|
5085
|
-
try {
|
|
5086
|
-
const result = await client2.canvaCreateDesign({
|
|
5087
|
-
title,
|
|
5088
|
-
designType: design_type,
|
|
5089
|
-
assetId: asset_id
|
|
5090
|
-
});
|
|
5091
|
-
const lines = [`Canva design created successfully.`];
|
|
5092
|
-
lines.push(`Design ID: ${result.designId}`);
|
|
5093
|
-
lines.push(`Edit URL: ${result.editUrl}`);
|
|
5094
|
-
if (result.thumbnailUrl) {
|
|
5095
|
-
lines.push(`Thumbnail: ${result.thumbnailUrl}`);
|
|
5096
|
-
}
|
|
5097
|
-
return {
|
|
5098
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5099
|
-
};
|
|
5100
|
-
} catch (error) {
|
|
5101
|
-
if (isNotConnectedError(error)) {
|
|
5102
|
-
return {
|
|
5103
|
-
content: [{ type: "text", text: CANVA_NOT_CONNECTED }]
|
|
5104
|
-
};
|
|
5105
|
-
}
|
|
5106
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5107
|
-
throw new Error(`Failed to create Canva design: ${message}`);
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
);
|
|
5111
|
-
server2.tool(
|
|
5112
|
-
"canva_export_design",
|
|
5113
|
-
"Export a Canva design to a downloadable file. Polls until the export is complete and returns the download URL.",
|
|
5114
|
-
{
|
|
5115
|
-
design_id: z18.string().min(1).describe("The Canva design ID to export."),
|
|
5116
|
-
format: z18.enum(["png", "pdf", "jpg"]).default("png").describe("Export format. Defaults to PNG.")
|
|
5117
|
-
},
|
|
5118
|
-
{
|
|
5119
|
-
title: "Export Canva Design",
|
|
5120
|
-
readOnlyHint: false,
|
|
5121
|
-
destructiveHint: false,
|
|
5122
|
-
idempotentHint: true,
|
|
5123
|
-
openWorldHint: true
|
|
5124
|
-
},
|
|
5125
|
-
async ({ design_id, format }) => {
|
|
5126
|
-
try {
|
|
5127
|
-
const result = await client2.canvaExportDesign({
|
|
5128
|
-
designId: design_id,
|
|
5129
|
-
format
|
|
5130
|
-
});
|
|
5131
|
-
return {
|
|
5132
|
-
content: [
|
|
5133
|
-
{
|
|
5134
|
-
type: "text",
|
|
5135
|
-
text: `Design exported successfully.
|
|
5136
|
-
Download URL: ${result.downloadUrl}`
|
|
5137
|
-
}
|
|
5138
|
-
]
|
|
5139
|
-
};
|
|
5140
|
-
} catch (error) {
|
|
5141
|
-
if (isNotConnectedError(error)) {
|
|
5142
|
-
return {
|
|
5143
|
-
content: [{ type: "text", text: CANVA_NOT_CONNECTED }]
|
|
5144
|
-
};
|
|
5145
|
-
}
|
|
5146
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5147
|
-
throw new Error(`Failed to export Canva design: ${message}`);
|
|
5148
|
-
}
|
|
5149
|
-
}
|
|
5150
|
-
);
|
|
5151
|
-
server2.tool(
|
|
5152
|
-
"canva_search_designs",
|
|
5153
|
-
"Search the customer's Canva designs by keyword. Returns matching designs with their IDs, titles, thumbnails, and edit URLs.",
|
|
5154
|
-
{
|
|
5155
|
-
query: z18.string().min(1).describe("Search keyword to find designs.")
|
|
5156
|
-
},
|
|
5157
|
-
{
|
|
5158
|
-
title: "Search Canva Designs",
|
|
5159
|
-
readOnlyHint: true,
|
|
5160
|
-
destructiveHint: false,
|
|
5161
|
-
idempotentHint: true,
|
|
5162
|
-
openWorldHint: true
|
|
5163
|
-
},
|
|
5164
|
-
async ({ query }) => {
|
|
5165
|
-
try {
|
|
5166
|
-
const designs = await client2.canvaSearchDesigns(query);
|
|
5167
|
-
if (!designs || designs.length === 0) {
|
|
5168
|
-
return {
|
|
5169
|
-
content: [
|
|
5170
|
-
{
|
|
5171
|
-
type: "text",
|
|
5172
|
-
text: `No Canva designs found matching "${query}".`
|
|
5173
|
-
}
|
|
5174
|
-
]
|
|
5175
|
-
};
|
|
5176
|
-
}
|
|
5177
|
-
const lines = [`## Canva Designs (${designs.length} results)`];
|
|
5178
|
-
lines.push("");
|
|
5179
|
-
for (const d of designs) {
|
|
5180
|
-
lines.push(`**${d.title}** (ID: ${d.id})`);
|
|
5181
|
-
lines.push(` Edit: ${d.editUrl}`);
|
|
5182
|
-
if (d.thumbnailUrl) lines.push(` Thumbnail: ${d.thumbnailUrl}`);
|
|
5183
|
-
lines.push("");
|
|
5184
|
-
}
|
|
5185
|
-
return {
|
|
5186
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5187
|
-
};
|
|
5188
|
-
} catch (error) {
|
|
5189
|
-
if (isNotConnectedError(error)) {
|
|
5190
|
-
return {
|
|
5191
|
-
content: [{ type: "text", text: CANVA_NOT_CONNECTED }]
|
|
5192
|
-
};
|
|
5193
|
-
}
|
|
5194
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5195
|
-
throw new Error(`Failed to search Canva designs: ${message}`);
|
|
5196
|
-
}
|
|
5197
|
-
}
|
|
5198
|
-
);
|
|
5199
|
-
server2.tool(
|
|
5200
|
-
"canva_get_design",
|
|
5201
|
-
"Get details about a specific Canva design by ID. Returns the design's title, edit URL, thumbnail, and metadata.",
|
|
5202
|
-
{
|
|
5203
|
-
design_id: z18.string().min(1).describe("The Canva design ID to retrieve.")
|
|
5204
|
-
},
|
|
5205
|
-
{
|
|
5206
|
-
title: "Get Canva Design",
|
|
5207
|
-
readOnlyHint: true,
|
|
5208
|
-
destructiveHint: false,
|
|
5209
|
-
idempotentHint: true,
|
|
5210
|
-
openWorldHint: true
|
|
5211
|
-
},
|
|
5212
|
-
async ({ design_id }) => {
|
|
5213
|
-
try {
|
|
5214
|
-
const design = await client2.canvaGetDesign(design_id);
|
|
5215
|
-
const lines = [`## ${design.title}`];
|
|
5216
|
-
lines.push(`Design ID: ${design.id}`);
|
|
5217
|
-
lines.push(`Edit URL: ${design.editUrl}`);
|
|
5218
|
-
lines.push(`View URL: ${design.viewUrl}`);
|
|
5219
|
-
if (design.thumbnailUrl) {
|
|
5220
|
-
lines.push(`Thumbnail: ${design.thumbnailUrl}`);
|
|
5221
|
-
}
|
|
5222
|
-
if (design.createdAt) lines.push(`Created: ${design.createdAt}`);
|
|
5223
|
-
if (design.updatedAt) lines.push(`Updated: ${design.updatedAt}`);
|
|
5224
|
-
return {
|
|
5225
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5226
|
-
};
|
|
5227
|
-
} catch (error) {
|
|
5228
|
-
if (isNotConnectedError(error)) {
|
|
5229
|
-
return {
|
|
5230
|
-
content: [{ type: "text", text: CANVA_NOT_CONNECTED }]
|
|
5231
|
-
};
|
|
5232
|
-
}
|
|
5233
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5234
|
-
throw new Error(`Failed to get Canva design: ${message}`);
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
5237
|
-
);
|
|
5238
|
-
}
|
|
5239
|
-
|
|
5240
|
-
// src/tools/sendgrid-newsletter.ts
|
|
5241
|
-
import { z as z19 } from "zod";
|
|
5242
|
-
function registerSendGridNewsletterTools(server2, client2, options) {
|
|
5243
|
-
const allowDirectSend2 = options?.allowDirectSend ?? false;
|
|
5244
|
-
server2.tool(
|
|
5245
|
-
"sg_list_lists",
|
|
5246
|
-
"List all SendGrid subscriber lists for the account. Returns list names, IDs, and descriptions.",
|
|
5247
|
-
{},
|
|
5248
|
-
{
|
|
5249
|
-
title: "List Subscriber Lists",
|
|
5250
|
-
readOnlyHint: true,
|
|
5251
|
-
destructiveHint: false,
|
|
5252
|
-
idempotentHint: true,
|
|
5253
|
-
openWorldHint: false
|
|
5254
|
-
},
|
|
5255
|
-
async () => {
|
|
5256
|
-
const lists = await client2.sgListLists();
|
|
5257
|
-
if (!lists || lists.length === 0) {
|
|
5258
|
-
return {
|
|
5259
|
-
content: [
|
|
5260
|
-
{
|
|
5261
|
-
type: "text",
|
|
5262
|
-
text: "No subscriber lists found. Create one with sg_create_list."
|
|
5263
|
-
}
|
|
5264
|
-
]
|
|
5265
|
-
};
|
|
5266
|
-
}
|
|
5267
|
-
const lines = [`## Subscriber Lists (${lists.length})`];
|
|
5268
|
-
for (const l of lists) {
|
|
5269
|
-
lines.push(`- **${l.name}** (ID: ${l.id})${l.description ? ` \u2014 ${l.description}` : ""}`);
|
|
5270
|
-
}
|
|
5271
|
-
return {
|
|
5272
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5273
|
-
};
|
|
5274
|
-
}
|
|
5275
|
-
);
|
|
5276
|
-
server2.tool(
|
|
5277
|
-
"sg_create_list",
|
|
5278
|
-
"Create a new subscriber list for organizing newsletter recipients.",
|
|
5279
|
-
{
|
|
5280
|
-
name: z19.string().min(1).max(255).describe("Name of the subscriber list."),
|
|
5281
|
-
description: z19.string().max(1e3).optional().describe("Optional description of the list.")
|
|
5282
|
-
},
|
|
5283
|
-
{
|
|
5284
|
-
title: "Create Subscriber List",
|
|
5285
|
-
readOnlyHint: false,
|
|
5286
|
-
destructiveHint: false,
|
|
5287
|
-
idempotentHint: false,
|
|
5288
|
-
openWorldHint: false
|
|
5289
|
-
},
|
|
5290
|
-
async ({ name, description }) => {
|
|
5291
|
-
const list = await client2.sgCreateList({ name, description });
|
|
5292
|
-
return {
|
|
5293
|
-
content: [
|
|
5294
|
-
{
|
|
5295
|
-
type: "text",
|
|
5296
|
-
text: `Subscriber list created.
|
|
5297
|
-
Name: ${list.name}
|
|
5298
|
-
ID: ${list.id}`
|
|
5299
|
-
}
|
|
5300
|
-
]
|
|
5301
|
-
};
|
|
5302
|
-
}
|
|
5303
|
-
);
|
|
5304
|
-
server2.tool(
|
|
5305
|
-
"sg_delete_list",
|
|
5306
|
-
"Delete a subscriber list. This removes the list and all memberships (subscribers themselves are not deleted). Requires confirmation.",
|
|
5307
|
-
{
|
|
5308
|
-
list_id: z19.number().int().describe("ID of the list to delete."),
|
|
5309
|
-
confirmed: z19.boolean().describe(
|
|
5310
|
-
"Must be true to confirm deletion. Show the list name to the user and ask for confirmation first."
|
|
5311
|
-
)
|
|
5312
|
-
},
|
|
5313
|
-
{
|
|
5314
|
-
title: "Delete Subscriber List",
|
|
5315
|
-
readOnlyHint: false,
|
|
5316
|
-
destructiveHint: true,
|
|
5317
|
-
idempotentHint: false,
|
|
5318
|
-
openWorldHint: false
|
|
5319
|
-
},
|
|
5320
|
-
async ({ list_id, confirmed }) => {
|
|
5321
|
-
if (!confirmed) {
|
|
5322
|
-
return {
|
|
5323
|
-
content: [
|
|
5324
|
-
{
|
|
5325
|
-
type: "text",
|
|
5326
|
-
text: "Deletion not confirmed. Set confirmed=true to proceed."
|
|
5327
|
-
}
|
|
5328
|
-
]
|
|
5329
|
-
};
|
|
5330
|
-
}
|
|
5331
|
-
await client2.sgDeleteList(list_id);
|
|
5332
|
-
return {
|
|
5333
|
-
content: [
|
|
5334
|
-
{
|
|
5335
|
-
type: "text",
|
|
5336
|
-
text: `Subscriber list ${list_id} deleted successfully.`
|
|
5337
|
-
}
|
|
5338
|
-
]
|
|
5339
|
-
};
|
|
5340
|
-
}
|
|
5341
|
-
);
|
|
5342
|
-
server2.tool(
|
|
5343
|
-
"sg_list_subscribers",
|
|
5344
|
-
"List SendGrid-managed subscribers. Can filter by list ID and status (active, unsubscribed, bounced, complained). Supports pagination.",
|
|
5345
|
-
{
|
|
5346
|
-
list_id: z19.number().int().optional().describe("Optional list ID to filter subscribers by list membership."),
|
|
5347
|
-
status: z19.enum(["active", "unsubscribed", "bounced", "complained"]).optional().describe("Optional status filter."),
|
|
5348
|
-
page: z19.number().int().min(1).default(1).describe("Page number."),
|
|
5349
|
-
limit: z19.number().int().min(1).max(100).default(50).describe("Results per page.")
|
|
5350
|
-
},
|
|
5351
|
-
{
|
|
5352
|
-
title: "List Subscribers",
|
|
5353
|
-
readOnlyHint: true,
|
|
5354
|
-
destructiveHint: false,
|
|
5355
|
-
idempotentHint: true,
|
|
5356
|
-
openWorldHint: false
|
|
5357
|
-
},
|
|
5358
|
-
async ({ list_id, status, page, limit }) => {
|
|
5359
|
-
const params = {
|
|
5360
|
-
page: String(page),
|
|
5361
|
-
limit: String(limit)
|
|
5362
|
-
};
|
|
5363
|
-
if (list_id !== void 0) params.listId = String(list_id);
|
|
5364
|
-
if (status) params.status = status;
|
|
5365
|
-
const result = await client2.sgListSubscribers(params);
|
|
5366
|
-
if (!result.subscribers || result.subscribers.length === 0) {
|
|
5367
|
-
return {
|
|
5368
|
-
content: [
|
|
5369
|
-
{
|
|
5370
|
-
type: "text",
|
|
5371
|
-
text: "No subscribers found matching the criteria."
|
|
5372
|
-
}
|
|
5373
|
-
]
|
|
5374
|
-
};
|
|
5375
|
-
}
|
|
5376
|
-
const lines = [`## Subscribers (page ${result.page})`];
|
|
5377
|
-
for (const s of result.subscribers) {
|
|
5378
|
-
const name = [s.firstName, s.lastName].filter(Boolean).join(" ");
|
|
5379
|
-
lines.push(
|
|
5380
|
-
`- ${s.email}${name ? ` (${name})` : ""} \u2014 ${s.status} (ID: ${s.id})`
|
|
5381
|
-
);
|
|
5382
|
-
}
|
|
5383
|
-
return {
|
|
5384
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5385
|
-
};
|
|
5386
|
-
}
|
|
5387
|
-
);
|
|
5388
|
-
server2.tool(
|
|
5389
|
-
"sg_add_subscriber",
|
|
5390
|
-
"Add or update a subscriber in the SendGrid-managed subscriber list. Optionally assign to one or more lists.",
|
|
5391
|
-
{
|
|
5392
|
-
email: z19.string().email().describe("Subscriber's email address."),
|
|
5393
|
-
first_name: z19.string().optional().describe("Subscriber's first name."),
|
|
5394
|
-
last_name: z19.string().optional().describe("Subscriber's last name."),
|
|
5395
|
-
list_ids: z19.array(z19.number().int()).optional().describe("Optional list IDs to add the subscriber to.")
|
|
5396
|
-
},
|
|
5397
|
-
{
|
|
5398
|
-
title: "Add Subscriber",
|
|
5399
|
-
readOnlyHint: false,
|
|
5400
|
-
destructiveHint: false,
|
|
5401
|
-
idempotentHint: true,
|
|
5402
|
-
openWorldHint: false
|
|
5403
|
-
},
|
|
5404
|
-
async ({ email, first_name, last_name, list_ids }) => {
|
|
5405
|
-
const subscriber = await client2.sgAddSubscriber({
|
|
5406
|
-
email,
|
|
5407
|
-
firstName: first_name,
|
|
5408
|
-
lastName: last_name,
|
|
5409
|
-
listIds: list_ids
|
|
5410
|
-
});
|
|
5411
|
-
return {
|
|
5412
|
-
content: [
|
|
5413
|
-
{
|
|
5414
|
-
type: "text",
|
|
5415
|
-
text: `Subscriber added/updated.
|
|
5416
|
-
Email: ${subscriber.email}
|
|
5417
|
-
ID: ${subscriber.id}`
|
|
5418
|
-
}
|
|
5419
|
-
]
|
|
5420
|
-
};
|
|
5421
|
-
}
|
|
5422
|
-
);
|
|
5423
|
-
server2.tool(
|
|
5424
|
-
"sg_remove_subscriber",
|
|
5425
|
-
"Remove a subscriber (marks as unsubscribed). Requires confirmation.",
|
|
5426
|
-
{
|
|
5427
|
-
subscriber_id: z19.number().int().describe("ID of the subscriber to remove."),
|
|
5428
|
-
confirmed: z19.boolean().describe(
|
|
5429
|
-
"Must be true to confirm. Show the subscriber email to the user first."
|
|
5430
|
-
)
|
|
5431
|
-
},
|
|
5432
|
-
{
|
|
5433
|
-
title: "Remove Subscriber",
|
|
5434
|
-
readOnlyHint: false,
|
|
5435
|
-
destructiveHint: true,
|
|
5436
|
-
idempotentHint: false,
|
|
5437
|
-
openWorldHint: false
|
|
5438
|
-
},
|
|
5439
|
-
async ({ subscriber_id, confirmed }) => {
|
|
5440
|
-
if (!confirmed) {
|
|
5441
|
-
return {
|
|
5442
|
-
content: [
|
|
5443
|
-
{
|
|
5444
|
-
type: "text",
|
|
5445
|
-
text: "Removal not confirmed. Set confirmed=true to proceed."
|
|
5446
|
-
}
|
|
5447
|
-
]
|
|
5448
|
-
};
|
|
5449
|
-
}
|
|
5450
|
-
const result = await client2.sgRemoveSubscriber(subscriber_id);
|
|
5451
|
-
return {
|
|
5452
|
-
content: [
|
|
5453
|
-
{
|
|
5454
|
-
type: "text",
|
|
5455
|
-
text: `Subscriber ${result.email} marked as unsubscribed.`
|
|
5456
|
-
}
|
|
5457
|
-
]
|
|
5458
|
-
};
|
|
5459
|
-
}
|
|
5460
|
-
);
|
|
5461
|
-
server2.tool(
|
|
5462
|
-
"sg_list_sends",
|
|
5463
|
-
"List newsletter sends (drafts, scheduled, sent). Filter by status.",
|
|
5464
|
-
{
|
|
5465
|
-
status: z19.enum(["draft", "scheduled", "sending", "sent", "cancelled"]).optional().describe("Optional status filter."),
|
|
5466
|
-
page: z19.number().int().min(1).default(1).describe("Page number."),
|
|
5467
|
-
limit: z19.number().int().min(1).max(100).default(20).describe("Results per page.")
|
|
5468
|
-
},
|
|
5469
|
-
{
|
|
5470
|
-
title: "List Newsletter Sends",
|
|
5471
|
-
readOnlyHint: true,
|
|
5472
|
-
destructiveHint: false,
|
|
5473
|
-
idempotentHint: true,
|
|
5474
|
-
openWorldHint: false
|
|
5475
|
-
},
|
|
5476
|
-
async ({ status, page, limit }) => {
|
|
5477
|
-
const params = {
|
|
5478
|
-
page: String(page),
|
|
5479
|
-
limit: String(limit)
|
|
5480
|
-
};
|
|
5481
|
-
if (status) params.status = status;
|
|
5482
|
-
const result = await client2.sgListSends(params);
|
|
5483
|
-
if (!result.sends || result.sends.length === 0) {
|
|
5484
|
-
return {
|
|
5485
|
-
content: [
|
|
5486
|
-
{ type: "text", text: "No newsletter sends found." }
|
|
5487
|
-
]
|
|
5488
|
-
};
|
|
5489
|
-
}
|
|
5490
|
-
const lines = [`## Newsletter Sends (${result.sends.length})`];
|
|
5491
|
-
for (const s of result.sends) {
|
|
5492
|
-
lines.push(
|
|
5493
|
-
`- **${s.subject}** (ID: ${s.id}) \u2014 ${s.status}${s.sentAt ? `, sent ${s.sentAt}` : ""}${s.scheduledAt ? `, scheduled ${s.scheduledAt}` : ""}`
|
|
5494
|
-
);
|
|
5495
|
-
if (s.status === "sent") {
|
|
5496
|
-
lines.push(` Delivered: ${s.delivered} | Opened: ${s.opened}`);
|
|
5497
|
-
}
|
|
5498
|
-
}
|
|
5499
|
-
return {
|
|
5500
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5501
|
-
};
|
|
5502
|
-
}
|
|
5503
|
-
);
|
|
5504
|
-
server2.tool(
|
|
5505
|
-
"sg_get_send",
|
|
5506
|
-
"Get details and delivery stats for a specific newsletter send.",
|
|
5507
|
-
{
|
|
5508
|
-
send_id: z19.number().int().describe("ID of the newsletter send.")
|
|
5509
|
-
},
|
|
5510
|
-
{
|
|
5511
|
-
title: "Get Newsletter Send",
|
|
5512
|
-
readOnlyHint: true,
|
|
5513
|
-
destructiveHint: false,
|
|
5514
|
-
idempotentHint: true,
|
|
5515
|
-
openWorldHint: false
|
|
5516
|
-
},
|
|
5517
|
-
async ({ send_id }) => {
|
|
5518
|
-
const send = await client2.sgGetSend(send_id);
|
|
5519
|
-
const lines = [
|
|
5520
|
-
`## ${send.subject}`,
|
|
5521
|
-
`ID: ${send.id}`,
|
|
5522
|
-
`From: ${send.fromName ? `${send.fromName} <${send.fromEmail}>` : send.fromEmail}`,
|
|
5523
|
-
`Status: ${send.status}`
|
|
5524
|
-
];
|
|
5525
|
-
if (send.sentAt) lines.push(`Sent: ${send.sentAt}`);
|
|
5526
|
-
if (send.scheduledAt) lines.push(`Scheduled: ${send.scheduledAt}`);
|
|
5527
|
-
lines.push("", "### Delivery Stats");
|
|
5528
|
-
lines.push(`Delivered: ${send.delivered}`);
|
|
5529
|
-
lines.push(`Opened: ${send.opened}`);
|
|
5530
|
-
lines.push(`Clicked: ${send.clicked}`);
|
|
5531
|
-
lines.push(`Bounced: ${send.bounced}`);
|
|
5532
|
-
lines.push(`Complaints: ${send.complained}`);
|
|
5533
|
-
lines.push(`Unsubscribed: ${send.unsubscribed}`);
|
|
5534
|
-
lines.push("");
|
|
5535
|
-
lines.push(`NEWSLETTER_HTML_PREVIEW: ${send.htmlContent}`);
|
|
5536
|
-
return {
|
|
5537
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5538
|
-
};
|
|
5539
|
-
}
|
|
5540
|
-
);
|
|
5541
|
-
server2.tool(
|
|
5542
|
-
"sg_create_send",
|
|
5543
|
-
"Create a new newsletter send draft. Must specify subject, HTML content, and from email.",
|
|
5544
|
-
{
|
|
5545
|
-
subject: z19.string().min(1).max(500).describe("Email subject line."),
|
|
5546
|
-
html_content: z19.string().min(1).describe("HTML content of the newsletter."),
|
|
5547
|
-
from_email: z19.string().email().describe("Sender email address (must be authenticated domain)."),
|
|
5548
|
-
from_name: z19.string().optional().describe("Sender display name."),
|
|
5549
|
-
list_id: z19.number().int().optional().describe("Optional list ID to send to. If omitted, sends to all active subscribers.")
|
|
5550
|
-
},
|
|
5551
|
-
{
|
|
5552
|
-
title: "Create Newsletter Draft",
|
|
5553
|
-
readOnlyHint: false,
|
|
5554
|
-
destructiveHint: false,
|
|
5555
|
-
idempotentHint: false,
|
|
5556
|
-
openWorldHint: false
|
|
5557
|
-
},
|
|
5558
|
-
async ({ subject, html_content, from_email, from_name, list_id }) => {
|
|
5559
|
-
const send = await client2.sgCreateSend({
|
|
5560
|
-
subject,
|
|
5561
|
-
htmlContent: html_content,
|
|
5562
|
-
fromEmail: from_email,
|
|
5563
|
-
fromName: from_name,
|
|
5564
|
-
listId: list_id
|
|
5565
|
-
});
|
|
5566
|
-
return {
|
|
5567
|
-
content: [
|
|
5568
|
-
{
|
|
5569
|
-
type: "text",
|
|
5570
|
-
text: `Newsletter draft created.
|
|
5571
|
-
ID: ${send.id}
|
|
5572
|
-
Subject: ${send.subject}
|
|
5573
|
-
Status: ${send.status}
|
|
5574
|
-
|
|
5575
|
-
Use sg_test_send to preview, or sg_send_newsletter to send.`
|
|
5576
|
-
}
|
|
5577
|
-
]
|
|
5578
|
-
};
|
|
5579
|
-
}
|
|
5580
|
-
);
|
|
5581
|
-
server2.tool(
|
|
5582
|
-
"sg_update_send",
|
|
5583
|
-
"Update a newsletter draft. Only drafts can be updated.",
|
|
5584
|
-
{
|
|
5585
|
-
send_id: z19.number().int().describe("ID of the send to update."),
|
|
5586
|
-
subject: z19.string().min(1).max(500).optional().describe("New subject line."),
|
|
5587
|
-
html_content: z19.string().min(1).optional().describe("New HTML content."),
|
|
5588
|
-
from_email: z19.string().email().optional().describe("New sender email."),
|
|
5589
|
-
from_name: z19.string().optional().describe("New sender name."),
|
|
5590
|
-
list_id: z19.number().int().nullable().optional().describe("New list ID, or null to send to all.")
|
|
5591
|
-
},
|
|
5592
|
-
{
|
|
5593
|
-
title: "Update Newsletter Draft",
|
|
5594
|
-
readOnlyHint: false,
|
|
5595
|
-
destructiveHint: false,
|
|
5596
|
-
idempotentHint: true,
|
|
5597
|
-
openWorldHint: false
|
|
5598
|
-
},
|
|
5599
|
-
async ({ send_id, subject, html_content, from_email, from_name, list_id }) => {
|
|
5600
|
-
const data = {};
|
|
5601
|
-
if (subject !== void 0) data.subject = subject;
|
|
5602
|
-
if (html_content !== void 0) data.htmlContent = html_content;
|
|
5603
|
-
if (from_email !== void 0) data.fromEmail = from_email;
|
|
5604
|
-
if (from_name !== void 0) data.fromName = from_name;
|
|
5605
|
-
if (list_id !== void 0) data.listId = list_id;
|
|
5606
|
-
const send = await client2.sgUpdateSend(send_id, data);
|
|
5607
|
-
return {
|
|
5608
|
-
content: [
|
|
5609
|
-
{
|
|
5610
|
-
type: "text",
|
|
5611
|
-
text: `Newsletter draft updated.
|
|
5612
|
-
ID: ${send.id}
|
|
5613
|
-
Subject: ${send.subject}`
|
|
5614
|
-
}
|
|
5615
|
-
]
|
|
5616
|
-
};
|
|
5617
|
-
}
|
|
5618
|
-
);
|
|
5619
|
-
server2.tool(
|
|
5620
|
-
"sg_delete_send",
|
|
5621
|
-
"Delete a newsletter draft. Only drafts can be deleted. Requires confirmation.",
|
|
5622
|
-
{
|
|
5623
|
-
send_id: z19.number().int().describe("ID of the draft to delete."),
|
|
5624
|
-
confirmed: z19.boolean().describe("Must be true to confirm deletion.")
|
|
5625
|
-
},
|
|
5626
|
-
{
|
|
5627
|
-
title: "Delete Newsletter Draft",
|
|
5628
|
-
readOnlyHint: false,
|
|
5629
|
-
destructiveHint: true,
|
|
5630
|
-
idempotentHint: false,
|
|
5631
|
-
openWorldHint: false
|
|
5632
|
-
},
|
|
5633
|
-
async ({ send_id, confirmed }) => {
|
|
5634
|
-
if (!confirmed) {
|
|
5635
|
-
return {
|
|
5636
|
-
content: [
|
|
5637
|
-
{
|
|
5638
|
-
type: "text",
|
|
5639
|
-
text: "Deletion not confirmed. Set confirmed=true to proceed."
|
|
5640
|
-
}
|
|
5641
|
-
]
|
|
5642
|
-
};
|
|
5643
|
-
}
|
|
5644
|
-
await client2.sgDeleteSend(send_id);
|
|
5645
|
-
return {
|
|
5646
|
-
content: [
|
|
5647
|
-
{
|
|
5648
|
-
type: "text",
|
|
5649
|
-
text: `Newsletter draft ${send_id} deleted.`
|
|
5650
|
-
}
|
|
5651
|
-
]
|
|
5652
|
-
};
|
|
5653
|
-
}
|
|
5654
|
-
);
|
|
5655
|
-
server2.tool(
|
|
5656
|
-
"sg_send_newsletter",
|
|
5657
|
-
allowDirectSend2 ? "Send a newsletter immediately to all active subscribers (or subscribers in the specified list). This action cannot be undone. Requires confirmation." : "DISABLED: Direct send is not enabled for this account. Contact support.",
|
|
5658
|
-
{
|
|
5659
|
-
send_id: z19.number().int().describe("ID of the draft to send."),
|
|
5660
|
-
confirmed: z19.boolean().describe(
|
|
5661
|
-
"Must be true. Show the user the subject, from address, and recipient count before confirming."
|
|
5662
|
-
)
|
|
5663
|
-
},
|
|
5664
|
-
{
|
|
5665
|
-
title: "Send Newsletter Now",
|
|
5666
|
-
readOnlyHint: false,
|
|
5667
|
-
destructiveHint: true,
|
|
5668
|
-
idempotentHint: false,
|
|
5669
|
-
openWorldHint: true
|
|
5670
|
-
},
|
|
5671
|
-
async ({ send_id, confirmed }) => {
|
|
5672
|
-
if (!allowDirectSend2) {
|
|
5673
|
-
return {
|
|
5674
|
-
content: [
|
|
5675
|
-
{
|
|
5676
|
-
type: "text",
|
|
5677
|
-
text: "Direct send is not enabled for this account. Contact support to enable this feature."
|
|
5678
|
-
}
|
|
5679
|
-
]
|
|
5680
|
-
};
|
|
5681
|
-
}
|
|
5682
|
-
if (!confirmed) {
|
|
5683
|
-
return {
|
|
5684
|
-
content: [
|
|
5685
|
-
{
|
|
5686
|
-
type: "text",
|
|
5687
|
-
text: "Send not confirmed. Review the newsletter details with sg_get_send first, then set confirmed=true."
|
|
5688
|
-
}
|
|
5689
|
-
]
|
|
5690
|
-
};
|
|
5691
|
-
}
|
|
5692
|
-
const result = await client2.sgSendNewsletter(send_id);
|
|
5693
|
-
return {
|
|
5694
|
-
content: [
|
|
5695
|
-
{
|
|
5696
|
-
type: "text",
|
|
5697
|
-
text: `Newsletter sent successfully!
|
|
5698
|
-
Subject: ${result.subject}
|
|
5699
|
-
Recipients: ${result.recipientCount}
|
|
5700
|
-
Status: ${result.status}`
|
|
5701
|
-
}
|
|
5702
|
-
]
|
|
5703
|
-
};
|
|
5704
|
-
}
|
|
5705
|
-
);
|
|
5706
|
-
server2.tool(
|
|
5707
|
-
"sg_test_send",
|
|
5708
|
-
"Send a test email of a newsletter draft to a specific email address. Subject will be prefixed with [TEST].",
|
|
5709
|
-
{
|
|
5710
|
-
send_id: z19.number().int().describe("ID of the newsletter send."),
|
|
5711
|
-
email: z19.string().email().describe("Email address to send the test to.")
|
|
5712
|
-
},
|
|
5713
|
-
{
|
|
5714
|
-
title: "Send Test Email",
|
|
5715
|
-
readOnlyHint: false,
|
|
5716
|
-
destructiveHint: false,
|
|
5717
|
-
idempotentHint: false,
|
|
5718
|
-
openWorldHint: true
|
|
5719
|
-
},
|
|
5720
|
-
async ({ send_id, email }) => {
|
|
5721
|
-
await client2.sgTestSend(send_id, { email });
|
|
5722
|
-
return {
|
|
5723
|
-
content: [
|
|
5724
|
-
{
|
|
5725
|
-
type: "text",
|
|
5726
|
-
text: `Test email sent to ${email}. Check your inbox.`
|
|
5727
|
-
}
|
|
5728
|
-
]
|
|
5729
|
-
};
|
|
5730
|
-
}
|
|
5731
|
-
);
|
|
5732
|
-
server2.tool(
|
|
5733
|
-
"sg_schedule_send",
|
|
5734
|
-
"Schedule a newsletter to be sent at a future date/time. Requires confirmation.",
|
|
5735
|
-
{
|
|
5736
|
-
send_id: z19.number().int().describe("ID of the draft to schedule."),
|
|
5737
|
-
scheduled_at: z19.string().describe(
|
|
5738
|
-
"ISO 8601 datetime for when to send (e.g., '2025-01-15T09:00:00Z'). Must be in the future."
|
|
5739
|
-
),
|
|
5740
|
-
confirmed: z19.boolean().describe("Must be true to confirm scheduling.")
|
|
5741
|
-
},
|
|
5742
|
-
{
|
|
5743
|
-
title: "Schedule Newsletter Send",
|
|
5744
|
-
readOnlyHint: false,
|
|
5745
|
-
destructiveHint: false,
|
|
5746
|
-
idempotentHint: false,
|
|
5747
|
-
openWorldHint: true
|
|
5748
|
-
},
|
|
5749
|
-
async ({ send_id, scheduled_at, confirmed }) => {
|
|
5750
|
-
if (!confirmed) {
|
|
5751
|
-
return {
|
|
5752
|
-
content: [
|
|
5753
|
-
{
|
|
5754
|
-
type: "text",
|
|
5755
|
-
text: "Scheduling not confirmed. Set confirmed=true to proceed."
|
|
5756
|
-
}
|
|
5757
|
-
]
|
|
5758
|
-
};
|
|
5759
|
-
}
|
|
5760
|
-
const result = await client2.sgScheduleSend(send_id, {
|
|
5761
|
-
scheduledAt: scheduled_at
|
|
5762
|
-
});
|
|
5763
|
-
return {
|
|
5764
|
-
content: [
|
|
5765
|
-
{
|
|
5766
|
-
type: "text",
|
|
5767
|
-
text: `Newsletter scheduled.
|
|
5768
|
-
Subject: ${result.subject}
|
|
5769
|
-
Scheduled for: ${result.scheduledAt}
|
|
5770
|
-
Recipients: ${result.recipientCount}`
|
|
5771
|
-
}
|
|
5772
|
-
]
|
|
5773
|
-
};
|
|
5774
|
-
}
|
|
5775
|
-
);
|
|
5776
|
-
server2.tool(
|
|
5777
|
-
"sg_authenticate_domain",
|
|
5778
|
-
"Start domain authentication with SendGrid. Returns DNS records that need to be added to your domain's DNS settings.",
|
|
5779
|
-
{
|
|
5780
|
-
domain: z19.string().min(1).describe("Domain to authenticate (e.g., 'example.com').")
|
|
5781
|
-
},
|
|
5782
|
-
{
|
|
5783
|
-
title: "Authenticate Domain",
|
|
5784
|
-
readOnlyHint: false,
|
|
5785
|
-
destructiveHint: false,
|
|
5786
|
-
idempotentHint: false,
|
|
5787
|
-
openWorldHint: true
|
|
5788
|
-
},
|
|
5789
|
-
async ({ domain }) => {
|
|
5790
|
-
const result = await client2.sgAuthenticateDomain(domain);
|
|
5791
|
-
const lines = [
|
|
5792
|
-
`Domain authentication started for **${result.domain}**.`,
|
|
5793
|
-
`Domain ID: ${result.id}`,
|
|
5794
|
-
"",
|
|
5795
|
-
"Add these DNS records to your domain:",
|
|
5796
|
-
"```",
|
|
5797
|
-
JSON.stringify(result.dnsRecords, null, 2),
|
|
5798
|
-
"```",
|
|
5799
|
-
"",
|
|
5800
|
-
"After adding the records, use sg_validate_domain to verify."
|
|
5801
|
-
];
|
|
5802
|
-
return {
|
|
5803
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5804
|
-
};
|
|
5805
|
-
}
|
|
5806
|
-
);
|
|
5807
|
-
server2.tool(
|
|
5808
|
-
"sg_validate_domain",
|
|
5809
|
-
"Validate DNS records for a domain that was previously set up for authentication.",
|
|
5810
|
-
{
|
|
5811
|
-
domain_id: z19.number().int().describe("Domain ID from sg_authenticate_domain.")
|
|
5812
|
-
},
|
|
5813
|
-
{
|
|
5814
|
-
title: "Validate Domain",
|
|
5815
|
-
readOnlyHint: true,
|
|
5816
|
-
destructiveHint: false,
|
|
5817
|
-
idempotentHint: true,
|
|
5818
|
-
openWorldHint: true
|
|
5819
|
-
},
|
|
5820
|
-
async ({ domain_id }) => {
|
|
5821
|
-
const result = await client2.sgValidateDomain(domain_id);
|
|
5822
|
-
if (result.valid) {
|
|
5823
|
-
return {
|
|
5824
|
-
content: [
|
|
5825
|
-
{
|
|
5826
|
-
type: "text",
|
|
5827
|
-
text: "Domain validation successful! Your domain is now authenticated for sending."
|
|
5828
|
-
}
|
|
5829
|
-
]
|
|
5830
|
-
};
|
|
5831
|
-
}
|
|
5832
|
-
return {
|
|
5833
|
-
content: [
|
|
5834
|
-
{
|
|
5835
|
-
type: "text",
|
|
5836
|
-
text: `Domain validation failed. Check your DNS records.
|
|
5837
|
-
|
|
5838
|
-
Results:
|
|
5839
|
-
${JSON.stringify(result.validationResults, null, 2)}`
|
|
5840
|
-
}
|
|
5841
|
-
]
|
|
5842
|
-
};
|
|
5843
|
-
}
|
|
5844
|
-
);
|
|
5845
|
-
server2.tool(
|
|
5846
|
-
"sg_list_domains",
|
|
5847
|
-
"List all authenticated sending domains.",
|
|
5848
|
-
{},
|
|
5849
|
-
{
|
|
5850
|
-
title: "List Authenticated Domains",
|
|
5851
|
-
readOnlyHint: true,
|
|
5852
|
-
destructiveHint: false,
|
|
5853
|
-
idempotentHint: true,
|
|
5854
|
-
openWorldHint: true
|
|
5855
|
-
},
|
|
5856
|
-
async () => {
|
|
5857
|
-
const domains = await client2.sgListDomains();
|
|
5858
|
-
if (!domains || domains.length === 0) {
|
|
5859
|
-
return {
|
|
5860
|
-
content: [
|
|
5861
|
-
{
|
|
5862
|
-
type: "text",
|
|
5863
|
-
text: "No authenticated domains found. Use sg_authenticate_domain to set one up."
|
|
5864
|
-
}
|
|
5865
|
-
]
|
|
5866
|
-
};
|
|
5867
|
-
}
|
|
5868
|
-
const lines = [`## Authenticated Domains (${domains.length})`];
|
|
5869
|
-
for (const d of domains) {
|
|
5870
|
-
lines.push(
|
|
5871
|
-
`- **${d.domain}** (ID: ${d.id}) \u2014 ${d.valid ? "Valid" : "Not verified"}`
|
|
5872
|
-
);
|
|
5873
|
-
}
|
|
5874
|
-
return {
|
|
5875
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
5876
|
-
};
|
|
5877
|
-
}
|
|
5878
|
-
);
|
|
5879
3318
|
}
|
|
5880
3319
|
|
|
5881
3320
|
// src/index.ts
|
|
@@ -5921,12 +3360,7 @@ registerNewsletterTemplateTools(server, client);
|
|
|
5921
3360
|
registerCalendarTools(server, client);
|
|
5922
3361
|
registerNotificationTools(server, client);
|
|
5923
3362
|
registerPublishingRulesTools(server, client);
|
|
5924
|
-
registerAuditLogTools(server, client);
|
|
5925
3363
|
registerSourceTools(server, client);
|
|
5926
3364
|
registerNewsletterAdvancedTools(server, client);
|
|
5927
|
-
registerCarouselTools(server, client);
|
|
5928
|
-
registerQueueTools(server, client);
|
|
5929
|
-
registerCanvaTools(server, client);
|
|
5930
|
-
registerSendGridNewsletterTools(server, client, { allowDirectSend });
|
|
5931
3365
|
var transport = new StdioServerTransport();
|
|
5932
3366
|
await server.connect(transport);
|