@buzzposter/mcp 0.1.13 → 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 +536 -1492
- package/dist/tools.d.ts +5 -38
- package/dist/tools.js +510 -1471
- 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);
|
|
@@ -101,32 +98,9 @@ var BuzzPosterClient = class {
|
|
|
101
98
|
});
|
|
102
99
|
}
|
|
103
100
|
// Media
|
|
104
|
-
async uploadMediaMultipart(filename, buffer, mimeType) {
|
|
105
|
-
const formData = new FormData();
|
|
106
|
-
formData.append("file", new Blob([buffer], { type: mimeType }), filename);
|
|
107
|
-
const url = new URL(`${this.baseUrl}/api/v1/media/upload`);
|
|
108
|
-
const res = await fetch(url, {
|
|
109
|
-
method: "POST",
|
|
110
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
111
|
-
body: formData
|
|
112
|
-
});
|
|
113
|
-
if (!res.ok) {
|
|
114
|
-
const errorBody = await res.json().catch(() => ({ message: res.statusText }));
|
|
115
|
-
const message = typeof errorBody === "object" && errorBody !== null && "message" in errorBody ? String(errorBody.message) : `API error (${res.status})`;
|
|
116
|
-
throw new Error(`BuzzPoster API error (${res.status}): ${message}`);
|
|
117
|
-
}
|
|
118
|
-
const text = await res.text();
|
|
119
|
-
return text ? JSON.parse(text) : void 0;
|
|
120
|
-
}
|
|
121
101
|
async uploadFromUrl(data) {
|
|
122
102
|
return this.request("POST", "/api/v1/media/upload-from-url", data);
|
|
123
103
|
}
|
|
124
|
-
async uploadBase64(data) {
|
|
125
|
-
return this.request("POST", "/api/v1/media/upload-base64", data);
|
|
126
|
-
}
|
|
127
|
-
async getUploadUrl(data) {
|
|
128
|
-
return this.request("POST", "/api/v1/media/presign", data);
|
|
129
|
-
}
|
|
130
104
|
async listMedia() {
|
|
131
105
|
return this.request("GET", "/api/v1/media");
|
|
132
106
|
}
|
|
@@ -165,9 +139,6 @@ var BuzzPosterClient = class {
|
|
|
165
139
|
async listTags() {
|
|
166
140
|
return this.request("GET", "/api/v1/newsletters/tags");
|
|
167
141
|
}
|
|
168
|
-
async listSequences() {
|
|
169
|
-
return this.request("GET", "/api/v1/newsletters/sequences");
|
|
170
|
-
}
|
|
171
142
|
async listForms() {
|
|
172
143
|
return this.request("GET", "/api/v1/newsletters/forms");
|
|
173
144
|
}
|
|
@@ -178,36 +149,15 @@ var BuzzPosterClient = class {
|
|
|
178
149
|
async listAutomations(params) {
|
|
179
150
|
return this.request("GET", "/api/v1/newsletters/automations", void 0, params);
|
|
180
151
|
}
|
|
181
|
-
async enrollInAutomation(automationId, data) {
|
|
182
|
-
return this.request("POST", `/api/v1/newsletters/automations/${automationId}/enroll`, data);
|
|
183
|
-
}
|
|
184
152
|
async listSegments(params) {
|
|
185
153
|
return this.request("GET", "/api/v1/newsletters/segments", void 0, params);
|
|
186
154
|
}
|
|
187
155
|
async getSegment(id, params) {
|
|
188
156
|
return this.request("GET", `/api/v1/newsletters/segments/${id}`, void 0, params);
|
|
189
157
|
}
|
|
190
|
-
async getSegmentMembers(segmentId, params) {
|
|
191
|
-
return this.request("GET", `/api/v1/newsletters/segments/${segmentId}/members`, void 0, params);
|
|
192
|
-
}
|
|
193
|
-
async deleteSegment(id) {
|
|
194
|
-
return this.request("DELETE", `/api/v1/newsletters/segments/${id}`);
|
|
195
|
-
}
|
|
196
|
-
async recalculateSegment(id) {
|
|
197
|
-
return this.request("POST", `/api/v1/newsletters/segments/${id}/recalculate`);
|
|
198
|
-
}
|
|
199
158
|
async listCustomFields() {
|
|
200
159
|
return this.request("GET", "/api/v1/newsletters/custom-fields");
|
|
201
160
|
}
|
|
202
|
-
async createCustomField(data) {
|
|
203
|
-
return this.request("POST", "/api/v1/newsletters/custom-fields", data);
|
|
204
|
-
}
|
|
205
|
-
async updateCustomField(id, data) {
|
|
206
|
-
return this.request("PUT", `/api/v1/newsletters/custom-fields/${id}`, data);
|
|
207
|
-
}
|
|
208
|
-
async deleteCustomField(id) {
|
|
209
|
-
return this.request("DELETE", `/api/v1/newsletters/custom-fields/${id}`);
|
|
210
|
-
}
|
|
211
161
|
async tagSubscriber(subscriberId, data) {
|
|
212
162
|
return this.request("POST", `/api/v1/newsletters/subscribers/${subscriberId}/tags`, data);
|
|
213
163
|
}
|
|
@@ -217,33 +167,12 @@ var BuzzPosterClient = class {
|
|
|
217
167
|
async deleteSubscriber(id) {
|
|
218
168
|
return this.request("DELETE", `/api/v1/newsletters/subscribers/${id}`);
|
|
219
169
|
}
|
|
220
|
-
async bulkCreateSubscribers(data) {
|
|
221
|
-
return this.request("POST", "/api/v1/newsletters/subscribers/bulk", data);
|
|
222
|
-
}
|
|
223
|
-
async getReferralProgram() {
|
|
224
|
-
return this.request("GET", "/api/v1/newsletters/referral-program");
|
|
225
|
-
}
|
|
226
|
-
async listEspTiers() {
|
|
227
|
-
return this.request("GET", "/api/v1/newsletters/tiers");
|
|
228
|
-
}
|
|
229
170
|
async listPostTemplates() {
|
|
230
171
|
return this.request("GET", "/api/v1/newsletters/post-templates");
|
|
231
172
|
}
|
|
232
173
|
async getPostAggregateStats() {
|
|
233
174
|
return this.request("GET", "/api/v1/newsletters/broadcasts/aggregate-stats");
|
|
234
175
|
}
|
|
235
|
-
async deleteBroadcast(id) {
|
|
236
|
-
return this.request("DELETE", `/api/v1/newsletters/broadcasts/${id}`);
|
|
237
|
-
}
|
|
238
|
-
async listEspWebhooks() {
|
|
239
|
-
return this.request("GET", "/api/v1/newsletters/webhooks");
|
|
240
|
-
}
|
|
241
|
-
async createEspWebhook(data) {
|
|
242
|
-
return this.request("POST", "/api/v1/newsletters/webhooks", data);
|
|
243
|
-
}
|
|
244
|
-
async deleteEspWebhook(id) {
|
|
245
|
-
return this.request("DELETE", `/api/v1/newsletters/webhooks/${id}`);
|
|
246
|
-
}
|
|
247
176
|
// Knowledge
|
|
248
177
|
async listKnowledge(params) {
|
|
249
178
|
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
@@ -279,6 +208,18 @@ var BuzzPosterClient = class {
|
|
|
279
208
|
async listTemplates() {
|
|
280
209
|
return this.request("GET", "/api/v1/templates");
|
|
281
210
|
}
|
|
211
|
+
async createTemplate(data) {
|
|
212
|
+
return this.request("POST", "/api/v1/templates", data);
|
|
213
|
+
}
|
|
214
|
+
async updateTemplate(id, data) {
|
|
215
|
+
return this.request("PUT", "/api/v1/templates/" + id, data);
|
|
216
|
+
}
|
|
217
|
+
async deleteTemplate(id) {
|
|
218
|
+
return this.request("DELETE", "/api/v1/templates/" + id);
|
|
219
|
+
}
|
|
220
|
+
async duplicateTemplate(id) {
|
|
221
|
+
return this.request("POST", "/api/v1/templates/" + id + "/duplicate");
|
|
222
|
+
}
|
|
282
223
|
// Newsletter Archive
|
|
283
224
|
async listNewsletterArchive(params) {
|
|
284
225
|
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
@@ -350,10 +291,6 @@ var BuzzPosterClient = class {
|
|
|
350
291
|
async getPublishingRules() {
|
|
351
292
|
return this.request("GET", "/api/v1/publishing-rules");
|
|
352
293
|
}
|
|
353
|
-
// Audit Log
|
|
354
|
-
async getAuditLog(params) {
|
|
355
|
-
return this.request("GET", "/api/v1/audit-log", void 0, params);
|
|
356
|
-
}
|
|
357
294
|
// Newsletter Validation
|
|
358
295
|
async validateNewsletter(data) {
|
|
359
296
|
return this.request("POST", "/api/v1/newsletters/validate", data);
|
|
@@ -366,22 +303,6 @@ var BuzzPosterClient = class {
|
|
|
366
303
|
async testBroadcast(id, data) {
|
|
367
304
|
return this.request("POST", `/api/v1/newsletters/broadcasts/${id}/test`, data);
|
|
368
305
|
}
|
|
369
|
-
// Carousel templates
|
|
370
|
-
async getCarouselTemplate() {
|
|
371
|
-
return this.request("GET", "/api/v1/carousel-templates");
|
|
372
|
-
}
|
|
373
|
-
async updateCarouselTemplate(data) {
|
|
374
|
-
return this.request("PUT", "/api/v1/carousel-templates", data);
|
|
375
|
-
}
|
|
376
|
-
async deleteCarouselTemplate() {
|
|
377
|
-
return this.request("DELETE", "/api/v1/carousel-templates");
|
|
378
|
-
}
|
|
379
|
-
async generateCarousel(data) {
|
|
380
|
-
return this.request("POST", "/api/v1/carousel-templates/generate", data);
|
|
381
|
-
}
|
|
382
|
-
async previewCarousel(data) {
|
|
383
|
-
return this.request("POST", "/api/v1/carousel-templates/preview", data);
|
|
384
|
-
}
|
|
385
306
|
};
|
|
386
307
|
|
|
387
308
|
// src/tools/posts.ts
|
|
@@ -389,23 +310,7 @@ import { z } from "zod";
|
|
|
389
310
|
function registerPostTools(server2, client2) {
|
|
390
311
|
server2.tool(
|
|
391
312
|
"post",
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
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.
|
|
395
|
-
|
|
396
|
-
REQUIRED WORKFLOW:
|
|
397
|
-
1. BEFORE creating any content, call get_brand_voice and get_audience to match the customer's tone
|
|
398
|
-
2. BEFORE publishing, call get_publishing_rules to check safety settings
|
|
399
|
-
3. ALWAYS set confirmed=false first to get a preview -- never set confirmed=true on the first call
|
|
400
|
-
4. Show the user a visual preview of the post before confirming
|
|
401
|
-
5. Only set confirmed=true after the user explicitly says to publish/post/send
|
|
402
|
-
6. If publishing_rules.social_default_action is 'draft', create as draft unless the user explicitly asks to publish
|
|
403
|
-
7. If publishing_rules.require_double_confirm_social is true, ask for confirmation twice before publishing
|
|
404
|
-
8. NEVER publish immediately without user confirmation, even if the user says "post this" -- always show the preview first
|
|
405
|
-
|
|
406
|
-
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.
|
|
407
|
-
|
|
408
|
-
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.",
|
|
409
314
|
{
|
|
410
315
|
content: z.string().optional().describe("The text content of the post"),
|
|
411
316
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -497,118 +402,9 @@ Call this tool again with confirmed=true to proceed.`;
|
|
|
497
402
|
};
|
|
498
403
|
}
|
|
499
404
|
);
|
|
500
|
-
server2.tool(
|
|
501
|
-
"cross_post",
|
|
502
|
-
`Post the same content to all connected platforms at once.
|
|
503
|
-
|
|
504
|
-
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.
|
|
505
|
-
|
|
506
|
-
REQUIRED WORKFLOW:
|
|
507
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
508
|
-
2. BEFORE publishing, call get_publishing_rules
|
|
509
|
-
3. ALWAYS set confirmed=false first to preview
|
|
510
|
-
4. Show the user a visual preview showing how the post will appear on each platform
|
|
511
|
-
5. This is a high-impact action (goes to ALL platforms) -- always require explicit confirmation
|
|
512
|
-
6. If publishing_rules.require_double_confirm_social is true, ask twice
|
|
513
|
-
|
|
514
|
-
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.`,
|
|
515
|
-
{
|
|
516
|
-
content: z.string().describe("The text content to post everywhere"),
|
|
517
|
-
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
518
|
-
confirmed: z.boolean().default(false).describe(
|
|
519
|
-
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
520
|
-
)
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
title: "Cross-Post to All Platforms",
|
|
524
|
-
readOnlyHint: false,
|
|
525
|
-
destructiveHint: false,
|
|
526
|
-
idempotentHint: false,
|
|
527
|
-
openWorldHint: true
|
|
528
|
-
},
|
|
529
|
-
async (args) => {
|
|
530
|
-
const accountsData = await client2.listAccounts();
|
|
531
|
-
const platforms = (accountsData.accounts ?? []).map(
|
|
532
|
-
(a) => a.platform
|
|
533
|
-
);
|
|
534
|
-
if (platforms.length === 0) {
|
|
535
|
-
return {
|
|
536
|
-
content: [
|
|
537
|
-
{
|
|
538
|
-
type: "text",
|
|
539
|
-
text: "No connected social accounts found. Connect accounts first."
|
|
540
|
-
}
|
|
541
|
-
]
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
if (args.confirmed !== true) {
|
|
545
|
-
let rulesText = "";
|
|
546
|
-
try {
|
|
547
|
-
const rules = await client2.getPublishingRules();
|
|
548
|
-
const blockedWords = rules.blockedWords ?? [];
|
|
549
|
-
const foundBlocked = [];
|
|
550
|
-
if (args.content && blockedWords.length > 0) {
|
|
551
|
-
const lower = args.content.toLowerCase();
|
|
552
|
-
for (const word of blockedWords) {
|
|
553
|
-
if (lower.includes(word.toLowerCase())) foundBlocked.push(word);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
rulesText = `
|
|
557
|
-
### Safety Checks
|
|
558
|
-
`;
|
|
559
|
-
rulesText += `- Blocked words: ${foundBlocked.length > 0 ? `**FAILED** (found: ${foundBlocked.join(", ")})` : "PASS"}
|
|
560
|
-
`;
|
|
561
|
-
rulesText += `- Default action: ${rules.socialDefaultAction ?? "draft"}
|
|
562
|
-
`;
|
|
563
|
-
rulesText += `- Double confirmation required: ${rules.requireDoubleConfirmSocial ? "yes" : "no"}
|
|
564
|
-
`;
|
|
565
|
-
} catch {
|
|
566
|
-
}
|
|
567
|
-
const preview = `## Cross-Post Preview
|
|
568
|
-
|
|
569
|
-
**Content:** "${args.content}"
|
|
570
|
-
**Platforms:** ${platforms.join(", ")} (${platforms.length} platforms)
|
|
571
|
-
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
572
|
-
` : "") + rulesText + `
|
|
573
|
-
**Action:** This will be published **immediately** to **all ${platforms.length} connected platforms**. This is a high-impact action.
|
|
574
|
-
|
|
575
|
-
Call this tool again with confirmed=true to proceed.`;
|
|
576
|
-
return { content: [{ type: "text", text: preview }] };
|
|
577
|
-
}
|
|
578
|
-
const body = {
|
|
579
|
-
content: args.content,
|
|
580
|
-
platforms: platforms.map((p) => ({ platform: p })),
|
|
581
|
-
publishNow: true
|
|
582
|
-
};
|
|
583
|
-
if (args.media_urls?.length) {
|
|
584
|
-
body.mediaItems = args.media_urls.map((url) => ({
|
|
585
|
-
type: url.match(/\.(mp4|mov|avi|webm)$/i) ? "video" : "image",
|
|
586
|
-
url
|
|
587
|
-
}));
|
|
588
|
-
}
|
|
589
|
-
const result = await client2.createPost(body);
|
|
590
|
-
return {
|
|
591
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
);
|
|
595
405
|
server2.tool(
|
|
596
406
|
"schedule_post",
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
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.
|
|
600
|
-
|
|
601
|
-
REQUIRED WORKFLOW:
|
|
602
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
603
|
-
2. BEFORE scheduling, call get_publishing_rules
|
|
604
|
-
3. ALWAYS set confirmed=false first to preview
|
|
605
|
-
4. Show the user a visual preview with the scheduled date/time before confirming
|
|
606
|
-
5. Only confirm after explicit user approval
|
|
607
|
-
6. If the user hasn't specified a time, suggest one based on get_queue time slots
|
|
608
|
-
|
|
609
|
-
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.
|
|
610
|
-
|
|
611
|
-
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.",
|
|
612
408
|
{
|
|
613
409
|
content: z.string().optional().describe("The text content of the post"),
|
|
614
410
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -684,7 +480,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
684
480
|
);
|
|
685
481
|
server2.tool(
|
|
686
482
|
"create_draft",
|
|
687
|
-
"Save a post as a draft without publishing.
|
|
483
|
+
"Save a post as a draft without publishing. No confirmation needed.",
|
|
688
484
|
{
|
|
689
485
|
content: z.string().optional().describe("The text content of the draft"),
|
|
690
486
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
@@ -727,7 +523,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
727
523
|
);
|
|
728
524
|
server2.tool(
|
|
729
525
|
"list_posts",
|
|
730
|
-
"List recent posts with
|
|
526
|
+
"List recent posts with status and platform details.",
|
|
731
527
|
{
|
|
732
528
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
733
529
|
limit: z.string().optional().describe("Number of posts to return")
|
|
@@ -751,7 +547,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
751
547
|
);
|
|
752
548
|
server2.tool(
|
|
753
549
|
"get_post",
|
|
754
|
-
"Get detailed information about a specific post.",
|
|
550
|
+
"Get detailed information about a specific post by ID.",
|
|
755
551
|
{
|
|
756
552
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
757
553
|
},
|
|
@@ -769,117 +565,13 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
769
565
|
};
|
|
770
566
|
}
|
|
771
567
|
);
|
|
772
|
-
server2.tool(
|
|
773
|
-
"retry_post",
|
|
774
|
-
"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.",
|
|
775
|
-
{
|
|
776
|
-
post_id: z.string().describe("The ID of the post to retry"),
|
|
777
|
-
confirmed: z.boolean().default(false).describe(
|
|
778
|
-
"Set to true to confirm retry. If false/missing, shows post details first."
|
|
779
|
-
)
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
title: "Retry Failed Post",
|
|
783
|
-
readOnlyHint: false,
|
|
784
|
-
destructiveHint: false,
|
|
785
|
-
idempotentHint: true,
|
|
786
|
-
openWorldHint: true
|
|
787
|
-
},
|
|
788
|
-
async (args) => {
|
|
789
|
-
if (args.confirmed !== true) {
|
|
790
|
-
const post = await client2.getPost(args.post_id);
|
|
791
|
-
const platforms = post.platforms ?? [];
|
|
792
|
-
const failed = platforms.filter((p) => p.status === "failed");
|
|
793
|
-
const published = platforms.filter((p) => p.status === "published");
|
|
794
|
-
let text = `## Retry Post Preview
|
|
795
|
-
|
|
796
|
-
`;
|
|
797
|
-
text += `**Post ID:** ${args.post_id}
|
|
798
|
-
`;
|
|
799
|
-
text += `**Content:** "${(post.content ?? "").substring(0, 100)}"
|
|
800
|
-
`;
|
|
801
|
-
text += `**Status:** ${post.status}
|
|
802
|
-
|
|
803
|
-
`;
|
|
804
|
-
if (published.length > 0) {
|
|
805
|
-
text += `**Already published on:** ${published.map((p) => p.platform).join(", ")}
|
|
806
|
-
`;
|
|
807
|
-
}
|
|
808
|
-
if (failed.length > 0) {
|
|
809
|
-
text += `**Failed on:** ${failed.map((p) => `${p.platform} (${p.error ?? "unknown error"})`).join(", ")}
|
|
810
|
-
`;
|
|
811
|
-
}
|
|
812
|
-
text += `
|
|
813
|
-
Retrying will only attempt the failed platforms. Call this tool again with confirmed=true to proceed.`;
|
|
814
|
-
return { content: [{ type: "text", text }] };
|
|
815
|
-
}
|
|
816
|
-
const result = await client2.retryPost(args.post_id);
|
|
817
|
-
return {
|
|
818
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
);
|
|
822
568
|
}
|
|
823
569
|
|
|
824
570
|
// src/tools/accounts.ts
|
|
825
571
|
function registerAccountTools(server2, client2) {
|
|
826
|
-
server2.tool(
|
|
827
|
-
"list_accounts",
|
|
828
|
-
"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.",
|
|
829
|
-
{},
|
|
830
|
-
{
|
|
831
|
-
title: "List Connected Accounts",
|
|
832
|
-
readOnlyHint: true,
|
|
833
|
-
destructiveHint: false,
|
|
834
|
-
idempotentHint: true,
|
|
835
|
-
openWorldHint: false
|
|
836
|
-
},
|
|
837
|
-
async () => {
|
|
838
|
-
const result = await client2.listAccounts();
|
|
839
|
-
const accounts = result.accounts ?? [];
|
|
840
|
-
const esp = result.esp;
|
|
841
|
-
if (accounts.length === 0 && !esp) {
|
|
842
|
-
return {
|
|
843
|
-
content: [{
|
|
844
|
-
type: "text",
|
|
845
|
-
text: "## Connected Accounts\n\nNo accounts connected. Connect social media accounts and/or an email service provider via the dashboard."
|
|
846
|
-
}]
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
let text = "## Connected Accounts\n\n";
|
|
850
|
-
text += "### Social Media\n";
|
|
851
|
-
if (accounts.length > 0) {
|
|
852
|
-
for (const a of accounts) {
|
|
853
|
-
const icon = a.isActive ? "\u2705" : "\u274C";
|
|
854
|
-
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
855
|
-
const name = a.username || a.displayName || a.platform;
|
|
856
|
-
text += `${icon} **${platform}** \u2014 @${name}
|
|
857
|
-
`;
|
|
858
|
-
}
|
|
859
|
-
} else {
|
|
860
|
-
text += "No social accounts connected.\n";
|
|
861
|
-
}
|
|
862
|
-
text += "\n### Email Service Provider (ESP)\n";
|
|
863
|
-
if (esp && esp.connected) {
|
|
864
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
865
|
-
text += `\u2705 **${provider}** \u2014 Connected`;
|
|
866
|
-
if (esp.publicationId) text += ` (Publication: ${esp.publicationId})`;
|
|
867
|
-
text += "\n";
|
|
868
|
-
} else if (esp) {
|
|
869
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
870
|
-
text += `\u26A0\uFE0F **${provider}** \u2014 Provider set but API key missing
|
|
871
|
-
`;
|
|
872
|
-
} else {
|
|
873
|
-
text += "No ESP configured.\n";
|
|
874
|
-
}
|
|
875
|
-
return {
|
|
876
|
-
content: [{ type: "text", text }]
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
);
|
|
880
572
|
server2.tool(
|
|
881
573
|
"check_accounts_health",
|
|
882
|
-
"Check
|
|
574
|
+
"Check health status of connected social accounts. Direct user to BuzzPoster web UI to reconnect broken accounts.",
|
|
883
575
|
{},
|
|
884
576
|
{
|
|
885
577
|
title: "Check Account Health",
|
|
@@ -932,7 +624,7 @@ import { z as z2 } from "zod";
|
|
|
932
624
|
function registerAnalyticsTools(server2, client2) {
|
|
933
625
|
server2.tool(
|
|
934
626
|
"get_analytics",
|
|
935
|
-
"Get
|
|
627
|
+
"Get social media post performance analytics. Filter by platform and date range.",
|
|
936
628
|
{
|
|
937
629
|
platform: z2.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook"),
|
|
938
630
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
@@ -963,7 +655,7 @@ import { z as z3 } from "zod";
|
|
|
963
655
|
function registerInboxTools(server2, client2) {
|
|
964
656
|
server2.tool(
|
|
965
657
|
"list_conversations",
|
|
966
|
-
"List DM conversations across all connected
|
|
658
|
+
"List DM conversations across all connected platforms.",
|
|
967
659
|
{
|
|
968
660
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
969
661
|
},
|
|
@@ -1005,12 +697,7 @@ function registerInboxTools(server2, client2) {
|
|
|
1005
697
|
);
|
|
1006
698
|
server2.tool(
|
|
1007
699
|
"reply_to_conversation",
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
REQUIRED WORKFLOW:
|
|
1011
|
-
1. ALWAYS set confirmed=false first to preview the reply
|
|
1012
|
-
2. Show the user the reply text and which platform/conversation it will be sent to
|
|
1013
|
-
3. Only confirm after explicit user approval`,
|
|
700
|
+
"Send a DM reply. Set confirmed=false to preview first.",
|
|
1014
701
|
{
|
|
1015
702
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
1016
703
|
message: z3.string().describe("The reply message text"),
|
|
@@ -1066,12 +753,7 @@ This will send a DM reply on the external platform. Call this tool again with co
|
|
|
1066
753
|
);
|
|
1067
754
|
server2.tool(
|
|
1068
755
|
"reply_to_comment",
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
REQUIRED WORKFLOW:
|
|
1072
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1073
|
-
2. Show the user the reply and the original comment for context
|
|
1074
|
-
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.",
|
|
1075
757
|
{
|
|
1076
758
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
1077
759
|
message: z3.string().describe("The reply text"),
|
|
@@ -1123,12 +805,7 @@ This will post a public reply. Call this tool again with confirmed=true to send.
|
|
|
1123
805
|
);
|
|
1124
806
|
server2.tool(
|
|
1125
807
|
"reply_to_review",
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
REQUIRED WORKFLOW:
|
|
1129
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1130
|
-
2. Show the user the reply, the original review, and the star rating for context
|
|
1131
|
-
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.",
|
|
1132
809
|
{
|
|
1133
810
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
1134
811
|
message: z3.string().describe("The reply text"),
|
|
@@ -1164,75 +841,10 @@ This will post a public reply to the review. Call this tool again with confirmed
|
|
|
1164
841
|
|
|
1165
842
|
// src/tools/media.ts
|
|
1166
843
|
import { z as z4 } from "zod";
|
|
1167
|
-
import { readFile } from "fs/promises";
|
|
1168
844
|
function registerMediaTools(server2, client2) {
|
|
1169
|
-
server2.tool(
|
|
1170
|
-
"upload_media",
|
|
1171
|
-
"Upload an image or video file from a local file path. Returns a CDN URL that can be used in posts.",
|
|
1172
|
-
{
|
|
1173
|
-
file_path: z4.string().describe("Absolute path to the file on the local filesystem")
|
|
1174
|
-
},
|
|
1175
|
-
{
|
|
1176
|
-
title: "Upload Media File",
|
|
1177
|
-
readOnlyHint: false,
|
|
1178
|
-
destructiveHint: false,
|
|
1179
|
-
idempotentHint: false,
|
|
1180
|
-
openWorldHint: false
|
|
1181
|
-
},
|
|
1182
|
-
async (args) => {
|
|
1183
|
-
const buffer = Buffer.from(await readFile(args.file_path));
|
|
1184
|
-
const filename = args.file_path.split("/").pop() ?? "upload";
|
|
1185
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
1186
|
-
const mimeMap = {
|
|
1187
|
-
jpg: "image/jpeg",
|
|
1188
|
-
jpeg: "image/jpeg",
|
|
1189
|
-
png: "image/png",
|
|
1190
|
-
gif: "image/gif",
|
|
1191
|
-
webp: "image/webp",
|
|
1192
|
-
mp4: "video/mp4",
|
|
1193
|
-
mov: "video/quicktime",
|
|
1194
|
-
avi: "video/x-msvideo",
|
|
1195
|
-
webm: "video/webm",
|
|
1196
|
-
pdf: "application/pdf"
|
|
1197
|
-
};
|
|
1198
|
-
const mimeType = mimeMap[ext] ?? "application/octet-stream";
|
|
1199
|
-
const result = await client2.uploadMediaMultipart(filename, buffer, mimeType);
|
|
1200
|
-
return {
|
|
1201
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
);
|
|
1205
|
-
server2.tool(
|
|
1206
|
-
"upload_from_claude",
|
|
1207
|
-
"Upload an image that the user has dropped or pasted into Claude. When a user shares an image in the conversation, Claude can read it natively as base64. Pass the base64 data, filename, and content_type to upload it to the BuzzPoster media library and get a CDN URL back. Do NOT show the base64 data to the user -- just call this tool silently and return the CDN URL.",
|
|
1208
|
-
{
|
|
1209
|
-
data: z4.string().describe("Base64-encoded image data (no data URI prefix needed, just the raw base64 string)"),
|
|
1210
|
-
filename: z4.string().describe("Filename including extension (e.g. photo.jpg)"),
|
|
1211
|
-
content_type: z4.string().describe("MIME type (e.g. image/jpeg, image/png, image/webp, image/gif)"),
|
|
1212
|
-
folder: z4.string().optional().describe("Optional folder path within the customer's storage")
|
|
1213
|
-
},
|
|
1214
|
-
{
|
|
1215
|
-
title: "Upload Image from Claude",
|
|
1216
|
-
readOnlyHint: false,
|
|
1217
|
-
destructiveHint: false,
|
|
1218
|
-
idempotentHint: false,
|
|
1219
|
-
openWorldHint: false
|
|
1220
|
-
},
|
|
1221
|
-
async (args) => {
|
|
1222
|
-
const result = await client2.uploadBase64({
|
|
1223
|
-
data: args.data,
|
|
1224
|
-
filename: args.filename,
|
|
1225
|
-
content_type: args.content_type,
|
|
1226
|
-
folder: args.folder
|
|
1227
|
-
});
|
|
1228
|
-
return {
|
|
1229
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
);
|
|
1233
845
|
server2.tool(
|
|
1234
846
|
"upload_from_url",
|
|
1235
|
-
"Upload media from a public URL
|
|
847
|
+
"Upload media from a public URL to storage. Returns a CDN URL for use in posts.",
|
|
1236
848
|
{
|
|
1237
849
|
url: z4.string().url().describe("Public URL of the image or video to upload"),
|
|
1238
850
|
filename: z4.string().optional().describe("Optional filename override (including extension)"),
|
|
@@ -1251,38 +863,18 @@ function registerMediaTools(server2, client2) {
|
|
|
1251
863
|
filename: args.filename,
|
|
1252
864
|
folder: args.folder
|
|
1253
865
|
});
|
|
866
|
+
const item = result;
|
|
1254
867
|
return {
|
|
1255
|
-
content: [
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
server2.tool(
|
|
1260
|
-
"get_upload_url",
|
|
1261
|
-
"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.",
|
|
1262
|
-
{
|
|
1263
|
-
filename: z4.string().describe("The filename including extension (e.g. photo.jpg)"),
|
|
1264
|
-
content_type: z4.string().describe("MIME type of the file (e.g. image/jpeg, video/mp4)")
|
|
1265
|
-
},
|
|
1266
|
-
{
|
|
1267
|
-
title: "Get Upload URL",
|
|
1268
|
-
readOnlyHint: true,
|
|
1269
|
-
destructiveHint: false,
|
|
1270
|
-
idempotentHint: false,
|
|
1271
|
-
openWorldHint: false
|
|
1272
|
-
},
|
|
1273
|
-
async (args) => {
|
|
1274
|
-
const result = await client2.getUploadUrl({
|
|
1275
|
-
filename: args.filename,
|
|
1276
|
-
content_type: args.content_type
|
|
1277
|
-
});
|
|
1278
|
-
return {
|
|
1279
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
868
|
+
content: [
|
|
869
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
870
|
+
...item.type === "image" && item.url && item.mimeType && !item.mimeType.startsWith("video/") ? [{ type: "image", url: item.url, mimeType: item.mimeType }] : []
|
|
871
|
+
]
|
|
1280
872
|
};
|
|
1281
873
|
}
|
|
1282
874
|
);
|
|
1283
875
|
server2.tool(
|
|
1284
876
|
"list_media",
|
|
1285
|
-
"List all uploaded media files
|
|
877
|
+
"List all uploaded media files.",
|
|
1286
878
|
{},
|
|
1287
879
|
{
|
|
1288
880
|
title: "List Media Library",
|
|
@@ -1293,19 +885,19 @@ function registerMediaTools(server2, client2) {
|
|
|
1293
885
|
},
|
|
1294
886
|
async () => {
|
|
1295
887
|
const result = await client2.listMedia();
|
|
888
|
+
const media = Array.isArray(result) ? result : result?.media ?? [];
|
|
889
|
+
const imageBlocks = media.filter((m) => m.type === "image" && m.url && m.mimeType && !m.mimeType.startsWith("video/")).map((m) => ({ type: "image", url: m.url, mimeType: m.mimeType }));
|
|
1296
890
|
return {
|
|
1297
|
-
content: [
|
|
891
|
+
content: [
|
|
892
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
893
|
+
...imageBlocks
|
|
894
|
+
]
|
|
1298
895
|
};
|
|
1299
896
|
}
|
|
1300
897
|
);
|
|
1301
898
|
server2.tool(
|
|
1302
899
|
"delete_media",
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
REQUIRED WORKFLOW:
|
|
1306
|
-
1. ALWAYS set confirmed=false first
|
|
1307
|
-
2. Show the user which file will be deleted (filename, URL, size)
|
|
1308
|
-
3. Only confirm after explicit approval`,
|
|
900
|
+
"Delete a media file. Set confirmed=false to preview first.",
|
|
1309
901
|
{
|
|
1310
902
|
key: z4.string().describe("The key/path of the media file to delete"),
|
|
1311
903
|
confirmed: z4.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
@@ -1339,7 +931,7 @@ import { z as z5 } from "zod";
|
|
|
1339
931
|
function registerNewsletterTools(server2, client2, options = {}) {
|
|
1340
932
|
server2.tool(
|
|
1341
933
|
"list_subscribers",
|
|
1342
|
-
"List email subscribers from
|
|
934
|
+
"List email subscribers from the connected ESP.",
|
|
1343
935
|
{
|
|
1344
936
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
1345
937
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
@@ -1363,7 +955,7 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1363
955
|
);
|
|
1364
956
|
server2.tool(
|
|
1365
957
|
"add_subscriber",
|
|
1366
|
-
"Add a new
|
|
958
|
+
"Add a new subscriber to the mailing list.",
|
|
1367
959
|
{
|
|
1368
960
|
email: z5.string().describe("Email address of the subscriber"),
|
|
1369
961
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
@@ -1389,7 +981,7 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1389
981
|
);
|
|
1390
982
|
server2.tool(
|
|
1391
983
|
"create_newsletter",
|
|
1392
|
-
|
|
984
|
+
"Push a newsletter to the user's ESP as a draft. Show an HTML preview to the user before calling this.",
|
|
1393
985
|
{
|
|
1394
986
|
subject: z5.string().describe("Email subject line"),
|
|
1395
987
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
@@ -1409,13 +1001,17 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1409
1001
|
previewText: args.preview_text
|
|
1410
1002
|
});
|
|
1411
1003
|
return {
|
|
1412
|
-
content: [
|
|
1004
|
+
content: [
|
|
1005
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
1006
|
+
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1007
|
+
${args.content}` }] : []
|
|
1008
|
+
]
|
|
1413
1009
|
};
|
|
1414
1010
|
}
|
|
1415
1011
|
);
|
|
1416
1012
|
server2.tool(
|
|
1417
1013
|
"update_newsletter",
|
|
1418
|
-
|
|
1014
|
+
"Update an existing newsletter draft.",
|
|
1419
1015
|
{
|
|
1420
1016
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to update"),
|
|
1421
1017
|
subject: z5.string().optional().describe("Updated subject line"),
|
|
@@ -1436,27 +1032,18 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
1436
1032
|
if (args.preview_text) data.previewText = args.preview_text;
|
|
1437
1033
|
const result = await client2.updateBroadcast(args.broadcast_id, data);
|
|
1438
1034
|
return {
|
|
1439
|
-
content: [
|
|
1035
|
+
content: [
|
|
1036
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
1037
|
+
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1038
|
+
${args.content}` }] : []
|
|
1039
|
+
]
|
|
1440
1040
|
};
|
|
1441
1041
|
}
|
|
1442
1042
|
);
|
|
1443
1043
|
if (options.allowDirectSend) {
|
|
1444
1044
|
server2.tool(
|
|
1445
1045
|
"send_newsletter",
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
REQUIRED WORKFLOW:
|
|
1449
|
-
1. ALWAYS call get_publishing_rules first
|
|
1450
|
-
2. NEVER send without showing a full visual preview of the newsletter first
|
|
1451
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt, not a send
|
|
1452
|
-
4. Tell the user exactly how many subscribers will receive this email
|
|
1453
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1454
|
-
- First: "Are you sure you want to send this to [X] subscribers?"
|
|
1455
|
-
- Second: "This is irreversible. Type 'send' to confirm."
|
|
1456
|
-
6. If publishing_rules.allow_immediate_send is false and the user asks to send immediately, suggest scheduling instead
|
|
1457
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview and subscriber count
|
|
1458
|
-
|
|
1459
|
-
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.",
|
|
1460
1047
|
{
|
|
1461
1048
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1462
1049
|
confirmed: z5.boolean().default(false).describe(
|
|
@@ -1520,22 +1107,7 @@ Call this tool again with confirmed=true to send.`;
|
|
|
1520
1107
|
}
|
|
1521
1108
|
server2.tool(
|
|
1522
1109
|
"schedule_newsletter",
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
REQUIRED WORKFLOW:
|
|
1526
|
-
1. ALWAYS call get_publishing_rules first
|
|
1527
|
-
2. NEVER schedule without showing a full visual preview of the newsletter first
|
|
1528
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt with details, not an actual schedule
|
|
1529
|
-
4. Tell the user exactly how many subscribers will receive this email and the scheduled send time
|
|
1530
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1531
|
-
- First: "Are you sure you want to schedule this for [time] to [X] subscribers?"
|
|
1532
|
-
- Second: "Confirm schedule for [time]."
|
|
1533
|
-
6. If publishing_rules.allow_immediate_send is false and the scheduled time is very soon, warn the user
|
|
1534
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview, subscriber count, and scheduled time
|
|
1535
|
-
|
|
1536
|
-
Scheduling can typically be cancelled or rescheduled later, but the user should still verify the time is correct before confirming.
|
|
1537
|
-
|
|
1538
|
-
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.",
|
|
1539
1111
|
{
|
|
1540
1112
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to schedule"),
|
|
1541
1113
|
scheduled_for: z5.string().describe(
|
|
@@ -1611,9 +1183,13 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1611
1183
|
);
|
|
1612
1184
|
server2.tool(
|
|
1613
1185
|
"list_newsletters",
|
|
1614
|
-
"List
|
|
1186
|
+
"List newsletters with status, dates, and subject. Supports filtering by status, date range, and keyword.",
|
|
1615
1187
|
{
|
|
1616
|
-
page: z5.string().optional().describe("Page number for pagination")
|
|
1188
|
+
page: z5.string().optional().describe("Page number for pagination"),
|
|
1189
|
+
status: z5.enum(["draft", "sent", "scheduled"]).optional().describe("Filter by status: draft, sent, or scheduled"),
|
|
1190
|
+
from_date: z5.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
1191
|
+
to_date: z5.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
1192
|
+
subject: z5.string().optional().describe("Keyword to search in subject lines (case-insensitive)")
|
|
1617
1193
|
},
|
|
1618
1194
|
{
|
|
1619
1195
|
title: "List Newsletters",
|
|
@@ -1625,15 +1201,37 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1625
1201
|
async (args) => {
|
|
1626
1202
|
const params = {};
|
|
1627
1203
|
if (args.page) params.page = args.page;
|
|
1204
|
+
if (args.status) params.status = args.status;
|
|
1205
|
+
if (args.from_date) params.fromDate = args.from_date;
|
|
1206
|
+
if (args.to_date) params.toDate = args.to_date;
|
|
1207
|
+
if (args.subject) params.subject = args.subject;
|
|
1628
1208
|
const result = await client2.listBroadcasts(params);
|
|
1629
|
-
|
|
1630
|
-
|
|
1209
|
+
const broadcasts = result?.broadcasts ?? [];
|
|
1210
|
+
if (broadcasts.length === 0) {
|
|
1211
|
+
return {
|
|
1212
|
+
content: [{ type: "text", text: "No newsletters found matching your filters." }]
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
let text = `## Newsletters (${broadcasts.length}`;
|
|
1216
|
+
if (result?.totalCount != null) text += ` of ${result.totalCount}`;
|
|
1217
|
+
text += ")\n\n";
|
|
1218
|
+
for (const b of broadcasts) {
|
|
1219
|
+
const status = (b.status ?? "unknown").toUpperCase();
|
|
1220
|
+
const date = b.sentAt ? new Date(b.sentAt).toLocaleString() : b.createdAt ? new Date(b.createdAt).toLocaleString() : "";
|
|
1221
|
+
text += `- **[${status}]** "${b.subject ?? "(no subject)"}"
|
|
1222
|
+
`;
|
|
1223
|
+
text += ` ID: ${b.id}`;
|
|
1224
|
+
if (date) text += ` | ${b.sentAt ? "Sent" : "Created"}: ${date}`;
|
|
1225
|
+
text += "\n";
|
|
1226
|
+
}
|
|
1227
|
+
return {
|
|
1228
|
+
content: [{ type: "text", text }]
|
|
1631
1229
|
};
|
|
1632
1230
|
}
|
|
1633
1231
|
);
|
|
1634
1232
|
server2.tool(
|
|
1635
1233
|
"list_tags",
|
|
1636
|
-
"List all subscriber tags
|
|
1234
|
+
"List all subscriber tags.",
|
|
1637
1235
|
{},
|
|
1638
1236
|
{
|
|
1639
1237
|
title: "List Subscriber Tags",
|
|
@@ -1649,27 +1247,9 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1649
1247
|
};
|
|
1650
1248
|
}
|
|
1651
1249
|
);
|
|
1652
|
-
server2.tool(
|
|
1653
|
-
"list_sequences",
|
|
1654
|
-
"List all email sequences/automations from your email service provider. On Beehiiv, this returns automations. For detailed automation data use list_automations instead.",
|
|
1655
|
-
{},
|
|
1656
|
-
{
|
|
1657
|
-
title: "List Email Sequences",
|
|
1658
|
-
readOnlyHint: true,
|
|
1659
|
-
destructiveHint: false,
|
|
1660
|
-
idempotentHint: true,
|
|
1661
|
-
openWorldHint: true
|
|
1662
|
-
},
|
|
1663
|
-
async () => {
|
|
1664
|
-
const result = await client2.listSequences();
|
|
1665
|
-
return {
|
|
1666
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
|
-
);
|
|
1670
1250
|
server2.tool(
|
|
1671
1251
|
"list_forms",
|
|
1672
|
-
"List all signup forms
|
|
1252
|
+
"List all signup forms.",
|
|
1673
1253
|
{},
|
|
1674
1254
|
{
|
|
1675
1255
|
title: "List Signup Forms",
|
|
@@ -1692,7 +1272,7 @@ import { z as z6 } from "zod";
|
|
|
1692
1272
|
function registerRssTools(server2, client2) {
|
|
1693
1273
|
server2.tool(
|
|
1694
1274
|
"fetch_feed",
|
|
1695
|
-
"Fetch and parse entries from an RSS or Atom feed URL.
|
|
1275
|
+
"Fetch and parse entries from an RSS or Atom feed URL.",
|
|
1696
1276
|
{
|
|
1697
1277
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
1698
1278
|
limit: z6.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
@@ -1713,7 +1293,7 @@ function registerRssTools(server2, client2) {
|
|
|
1713
1293
|
);
|
|
1714
1294
|
server2.tool(
|
|
1715
1295
|
"fetch_article",
|
|
1716
|
-
"Extract
|
|
1296
|
+
"Extract full article content from a URL as clean text.",
|
|
1717
1297
|
{
|
|
1718
1298
|
url: z6.string().describe("The article URL to extract content from")
|
|
1719
1299
|
},
|
|
@@ -1737,7 +1317,7 @@ function registerRssTools(server2, client2) {
|
|
|
1737
1317
|
function registerAccountInfoTool(server2, client2) {
|
|
1738
1318
|
server2.tool(
|
|
1739
1319
|
"get_account",
|
|
1740
|
-
"Get
|
|
1320
|
+
"Get account details, subscription status, ESP config, and all connected social/ESP accounts.",
|
|
1741
1321
|
{},
|
|
1742
1322
|
{
|
|
1743
1323
|
title: "Get Account Details",
|
|
@@ -1747,9 +1327,15 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
1747
1327
|
openWorldHint: false
|
|
1748
1328
|
},
|
|
1749
1329
|
async () => {
|
|
1750
|
-
const
|
|
1330
|
+
const [account, accountsData] = await Promise.all([
|
|
1331
|
+
client2.getAccount(),
|
|
1332
|
+
client2.listAccounts()
|
|
1333
|
+
]);
|
|
1751
1334
|
return {
|
|
1752
|
-
content: [{
|
|
1335
|
+
content: [{
|
|
1336
|
+
type: "text",
|
|
1337
|
+
text: JSON.stringify({ account, connectedAccounts: accountsData }, null, 2)
|
|
1338
|
+
}]
|
|
1753
1339
|
};
|
|
1754
1340
|
}
|
|
1755
1341
|
);
|
|
@@ -1760,7 +1346,7 @@ import { z as z7 } from "zod";
|
|
|
1760
1346
|
function registerBrandVoiceTools(server2, client2) {
|
|
1761
1347
|
server2.tool(
|
|
1762
1348
|
"get_brand_voice",
|
|
1763
|
-
"Get the
|
|
1349
|
+
"Get the brand voice profile and writing rules. Call before creating content.",
|
|
1764
1350
|
{},
|
|
1765
1351
|
{
|
|
1766
1352
|
title: "Get Brand Voice",
|
|
@@ -1835,21 +1421,14 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1835
1421
|
);
|
|
1836
1422
|
server2.tool(
|
|
1837
1423
|
"update_brand_voice",
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
-
|
|
1845
|
-
|
|
1846
|
-
{
|
|
1847
|
-
name: z7.string().max(80).optional().describe("Brand voice profile name (e.g. 'BOQtoday Voice', 'Lightbreak Editorial')"),
|
|
1848
|
-
description: z7.string().max(16e3).optional().describe("Overall voice description \u2014 tone, personality, style overview"),
|
|
1849
|
-
dos: z7.array(z7.string()).max(50).optional().describe("Writing rules to follow (array of do's). Send the FULL list, not just additions."),
|
|
1850
|
-
donts: z7.array(z7.string()).max(50).optional().describe("Things to avoid (array of don'ts). Send the FULL list, not just additions."),
|
|
1851
|
-
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" }'),
|
|
1852
|
-
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"),
|
|
1853
1432
|
confirmed: z7.boolean().default(false).describe(
|
|
1854
1433
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
1855
1434
|
)
|
|
@@ -1867,7 +1446,19 @@ USAGE GUIDELINES:
|
|
|
1867
1446
|
if (args.description !== void 0) payload.description = args.description;
|
|
1868
1447
|
if (args.dos !== void 0) payload.dos = args.dos;
|
|
1869
1448
|
if (args.donts !== void 0) payload.donts = args.donts;
|
|
1870
|
-
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
|
+
}
|
|
1871
1462
|
if (args.example_posts !== void 0) payload.examplePosts = args.example_posts;
|
|
1872
1463
|
if (Object.keys(payload).length === 0) {
|
|
1873
1464
|
return {
|
|
@@ -1955,7 +1546,7 @@ import { z as z8 } from "zod";
|
|
|
1955
1546
|
function registerKnowledgeTools(server2, client2) {
|
|
1956
1547
|
server2.tool(
|
|
1957
1548
|
"get_knowledge_base",
|
|
1958
|
-
"Get all items from the
|
|
1549
|
+
"Get all items from the knowledge base.",
|
|
1959
1550
|
{},
|
|
1960
1551
|
{
|
|
1961
1552
|
title: "Get Knowledge Base",
|
|
@@ -2009,7 +1600,7 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
2009
1600
|
);
|
|
2010
1601
|
server2.tool(
|
|
2011
1602
|
"search_knowledge",
|
|
2012
|
-
"Search the
|
|
1603
|
+
"Search the knowledge base by tag or keyword.",
|
|
2013
1604
|
{
|
|
2014
1605
|
query: z8.string().describe(
|
|
2015
1606
|
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
@@ -2060,7 +1651,7 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
2060
1651
|
);
|
|
2061
1652
|
server2.tool(
|
|
2062
1653
|
"add_knowledge",
|
|
2063
|
-
"Add reference material to the
|
|
1654
|
+
"Add reference material to the knowledge base. Include tags for categorization.",
|
|
2064
1655
|
{
|
|
2065
1656
|
title: z8.string().describe("Title for the knowledge item"),
|
|
2066
1657
|
content: z8.string().describe("The content/text to save"),
|
|
@@ -2102,10 +1693,8 @@ import { z as z9 } from "zod";
|
|
|
2102
1693
|
function registerAudienceTools(server2, client2) {
|
|
2103
1694
|
server2.tool(
|
|
2104
1695
|
"get_audience",
|
|
2105
|
-
"Get the target audience profile
|
|
2106
|
-
{
|
|
2107
|
-
audienceId: z9.string().optional().describe("Specific audience profile ID. If omitted, returns the default audience.")
|
|
2108
|
-
},
|
|
1696
|
+
"Get the target audience profile. Call before creating content.",
|
|
1697
|
+
{},
|
|
2109
1698
|
{
|
|
2110
1699
|
title: "Get Audience Profile",
|
|
2111
1700
|
readOnlyHint: true,
|
|
@@ -2113,9 +1702,9 @@ function registerAudienceTools(server2, client2) {
|
|
|
2113
1702
|
idempotentHint: true,
|
|
2114
1703
|
openWorldHint: false
|
|
2115
1704
|
},
|
|
2116
|
-
async (
|
|
1705
|
+
async () => {
|
|
2117
1706
|
try {
|
|
2118
|
-
const audience =
|
|
1707
|
+
const audience = await client2.getDefaultAudience();
|
|
2119
1708
|
const lines = [];
|
|
2120
1709
|
lines.push(`## Target Audience: ${audience.name}`);
|
|
2121
1710
|
lines.push("");
|
|
@@ -2179,25 +1768,16 @@ function registerAudienceTools(server2, client2) {
|
|
|
2179
1768
|
);
|
|
2180
1769
|
server2.tool(
|
|
2181
1770
|
"update_audience",
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
USAGE GUIDELINES:
|
|
2185
|
-
- Call get_audience first to see the current state before making changes
|
|
2186
|
-
- Always set confirmed=false first to preview the changes, then confirmed=true after user approval
|
|
2187
|
-
- When updating array fields (pain_points, motivations, preferred_platforms), include the FULL array (existing + new items), not just the new ones
|
|
2188
|
-
- Omit audience_id to update the default audience or create a new one if none exists
|
|
2189
|
-
- Max limits: name 100 chars, description 500 chars`,
|
|
1771
|
+
"Update the audience profile. Set confirmed=false to preview first.",
|
|
2190
1772
|
{
|
|
2191
|
-
|
|
2192
|
-
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."),
|
|
2193
1774
|
description: z9.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
2194
|
-
demographics: z9.string().optional().describe("Demographic details
|
|
2195
|
-
pain_points: z9.array(z9.string()).optional().describe("Problems
|
|
2196
|
-
motivations: z9.array(z9.string()).optional().describe("Goals
|
|
2197
|
-
preferred_platforms: z9.array(z9.string()).optional().describe("Social platforms the audience is most active on
|
|
2198
|
-
tone_notes: z9.string().optional().describe("How to speak to this audience
|
|
2199
|
-
content_preferences: z9.string().optional().describe("What content formats and topics resonate
|
|
2200
|
-
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"),
|
|
2201
1781
|
confirmed: z9.boolean().default(false).describe(
|
|
2202
1782
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
2203
1783
|
)
|
|
@@ -2219,27 +1799,24 @@ USAGE GUIDELINES:
|
|
|
2219
1799
|
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
2220
1800
|
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
2221
1801
|
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2222
|
-
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
2223
1802
|
if (Object.keys(payload).length === 0) {
|
|
2224
1803
|
return {
|
|
2225
1804
|
content: [
|
|
2226
1805
|
{
|
|
2227
1806
|
type: "text",
|
|
2228
|
-
text: "No fields provided. Specify at least one field to update
|
|
1807
|
+
text: "No fields provided. Specify at least one field to update."
|
|
2229
1808
|
}
|
|
2230
1809
|
],
|
|
2231
1810
|
isError: true
|
|
2232
1811
|
};
|
|
2233
1812
|
}
|
|
2234
|
-
let targetId
|
|
1813
|
+
let targetId;
|
|
2235
1814
|
let isCreating = false;
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
isCreating = true;
|
|
2242
|
-
}
|
|
1815
|
+
try {
|
|
1816
|
+
const defaultAudience = await client2.getDefaultAudience();
|
|
1817
|
+
targetId = String(defaultAudience.id);
|
|
1818
|
+
} catch {
|
|
1819
|
+
isCreating = true;
|
|
2243
1820
|
}
|
|
2244
1821
|
if (isCreating && !payload.name) {
|
|
2245
1822
|
return {
|
|
@@ -2255,9 +1832,6 @@ USAGE GUIDELINES:
|
|
|
2255
1832
|
if (args.confirmed !== true) {
|
|
2256
1833
|
const lines2 = [];
|
|
2257
1834
|
lines2.push(isCreating ? "## New Audience Preview" : "## Audience Update Preview");
|
|
2258
|
-
if (!isCreating) {
|
|
2259
|
-
lines2.push(`**Audience ID:** ${targetId}`);
|
|
2260
|
-
}
|
|
2261
1835
|
lines2.push("");
|
|
2262
1836
|
if (payload.name) {
|
|
2263
1837
|
lines2.push(`**Name:** ${payload.name}`);
|
|
@@ -2305,10 +1879,6 @@ USAGE GUIDELINES:
|
|
|
2305
1879
|
lines2.push(String(payload.contentPreferences));
|
|
2306
1880
|
lines2.push("");
|
|
2307
1881
|
}
|
|
2308
|
-
if (payload.isDefault !== void 0) {
|
|
2309
|
-
lines2.push(`**Set as default:** ${payload.isDefault ? "Yes" : "No"}`);
|
|
2310
|
-
lines2.push("");
|
|
2311
|
-
}
|
|
2312
1882
|
lines2.push("---");
|
|
2313
1883
|
lines2.push("Call this tool again with **confirmed=true** to save these changes.");
|
|
2314
1884
|
return {
|
|
@@ -2334,7 +1904,6 @@ USAGE GUIDELINES:
|
|
|
2334
1904
|
if (payload.platforms) summary.push(`${payload.platforms.length} platforms`);
|
|
2335
1905
|
if (payload.toneNotes) summary.push("tone notes");
|
|
2336
1906
|
if (payload.contentPreferences) summary.push("content preferences");
|
|
2337
|
-
if (payload.isDefault !== void 0) summary.push("default status");
|
|
2338
1907
|
lines.push(`**${isCreating ? "Created with" : "Updated"}:** ${summary.join(", ")}`);
|
|
2339
1908
|
return {
|
|
2340
1909
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
@@ -2378,7 +1947,7 @@ Follow these rules when drafting:
|
|
|
2378
1947
|
function registerNewsletterTemplateTools(server2, client2) {
|
|
2379
1948
|
server2.tool(
|
|
2380
1949
|
"get_newsletter_template",
|
|
2381
|
-
"Get
|
|
1950
|
+
"Get a newsletter template's structure, sections, and HTML. Pass templateId or omit for default.",
|
|
2382
1951
|
{
|
|
2383
1952
|
templateId: z10.string().optional().describe(
|
|
2384
1953
|
"Specific template ID. If omitted, returns the default template."
|
|
@@ -2479,6 +2048,15 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2479
2048
|
);
|
|
2480
2049
|
lines.push("");
|
|
2481
2050
|
}
|
|
2051
|
+
if (template.htmlContent) {
|
|
2052
|
+
lines.push("### HTML Template:");
|
|
2053
|
+
lines.push("The following HTML skeleton contains {{placeholder}} variables. Fill in the placeholders with real content when generating the newsletter.");
|
|
2054
|
+
lines.push("");
|
|
2055
|
+
lines.push("```html");
|
|
2056
|
+
lines.push(template.htmlContent);
|
|
2057
|
+
lines.push("```");
|
|
2058
|
+
lines.push("");
|
|
2059
|
+
}
|
|
2482
2060
|
if (template.rssFeedUrls && template.rssFeedUrls.length > 0) {
|
|
2483
2061
|
lines.push("### Linked RSS Feeds:");
|
|
2484
2062
|
for (const url of template.rssFeedUrls) {
|
|
@@ -2515,9 +2093,10 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2515
2093
|
);
|
|
2516
2094
|
server2.tool(
|
|
2517
2095
|
"get_past_newsletters",
|
|
2518
|
-
"
|
|
2096
|
+
"Get past newsletters from the archive. Pass newsletterId to get full content of a specific edition.",
|
|
2519
2097
|
{
|
|
2520
|
-
|
|
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."),
|
|
2521
2100
|
templateId: z10.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
2522
2101
|
},
|
|
2523
2102
|
{
|
|
@@ -2527,7 +2106,41 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2527
2106
|
idempotentHint: true,
|
|
2528
2107
|
openWorldHint: false
|
|
2529
2108
|
},
|
|
2530
|
-
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
|
+
}
|
|
2531
2144
|
try {
|
|
2532
2145
|
const params = {};
|
|
2533
2146
|
if (limit) params.limit = String(Math.min(limit, 50));
|
|
@@ -2565,12 +2178,17 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2565
2178
|
const textContent = nl.contentHtml ? nl.contentHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 500) : "(no content)";
|
|
2566
2179
|
lines.push(textContent);
|
|
2567
2180
|
lines.push(
|
|
2568
|
-
`[Full content available
|
|
2181
|
+
`[Full content available by calling get_past_newsletters with newsletterId: ${nl.id}]`
|
|
2569
2182
|
);
|
|
2570
2183
|
lines.push("");
|
|
2571
2184
|
}
|
|
2185
|
+
const mostRecent = newsletters[0];
|
|
2572
2186
|
return {
|
|
2573
|
-
content: [
|
|
2187
|
+
content: [
|
|
2188
|
+
{ type: "text", text: lines.join("\n") },
|
|
2189
|
+
...mostRecent?.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2190
|
+
${mostRecent.contentHtml}` }] : []
|
|
2191
|
+
]
|
|
2574
2192
|
};
|
|
2575
2193
|
} catch (error) {
|
|
2576
2194
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -2579,87 +2197,247 @@ function registerNewsletterTemplateTools(server2, client2) {
|
|
|
2579
2197
|
}
|
|
2580
2198
|
);
|
|
2581
2199
|
server2.tool(
|
|
2582
|
-
"
|
|
2583
|
-
"
|
|
2200
|
+
"save_newsletter",
|
|
2201
|
+
"Save an approved newsletter to the archive for future reference.",
|
|
2202
|
+
{
|
|
2203
|
+
subject: z10.string().describe("The newsletter subject line."),
|
|
2204
|
+
contentHtml: z10.string().describe("The full HTML content of the newsletter."),
|
|
2205
|
+
templateId: z10.string().optional().describe("The template ID used to generate this newsletter."),
|
|
2206
|
+
notes: z10.string().optional().describe(
|
|
2207
|
+
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2208
|
+
)
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
title: "Save Newsletter to Archive",
|
|
2212
|
+
readOnlyHint: false,
|
|
2213
|
+
destructiveHint: false,
|
|
2214
|
+
idempotentHint: false,
|
|
2215
|
+
openWorldHint: false
|
|
2216
|
+
},
|
|
2217
|
+
async ({ subject, contentHtml, templateId, notes }) => {
|
|
2218
|
+
try {
|
|
2219
|
+
const data = {
|
|
2220
|
+
subject,
|
|
2221
|
+
contentHtml
|
|
2222
|
+
};
|
|
2223
|
+
if (templateId) data.templateId = Number(templateId);
|
|
2224
|
+
if (notes) data.notes = notes;
|
|
2225
|
+
await client2.saveNewsletterToArchive(data);
|
|
2226
|
+
return {
|
|
2227
|
+
content: [
|
|
2228
|
+
{
|
|
2229
|
+
type: "text",
|
|
2230
|
+
text: `Newsletter "${subject}" has been saved to the archive successfully.`
|
|
2231
|
+
},
|
|
2232
|
+
...contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2233
|
+
${contentHtml}` }] : []
|
|
2234
|
+
]
|
|
2235
|
+
};
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2238
|
+
throw new Error(`Failed to save newsletter: ${message}`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
);
|
|
2242
|
+
server2.tool(
|
|
2243
|
+
"save_newsletter_template",
|
|
2244
|
+
"Create or update a newsletter template with HTML and section definitions.",
|
|
2584
2245
|
{
|
|
2585
|
-
|
|
2246
|
+
template_id: z10.string().optional().describe(
|
|
2247
|
+
"If provided, updates this existing template. Otherwise creates a new one."
|
|
2248
|
+
),
|
|
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."
|
|
2253
|
+
),
|
|
2254
|
+
sections: z10.array(
|
|
2255
|
+
z10.object({
|
|
2256
|
+
name: z10.string().describe("Section identifier, e.g. 'intro'"),
|
|
2257
|
+
label: z10.string().describe("Display label, e.g. 'Intro/Greeting'"),
|
|
2258
|
+
required: z10.boolean().optional().describe("Whether this section is required"),
|
|
2259
|
+
item_count: z10.number().optional().describe("Number of items in this section, if applicable")
|
|
2260
|
+
})
|
|
2261
|
+
).max(20).optional().describe("Array of section metadata describing the template structure"),
|
|
2262
|
+
style_config: z10.object({
|
|
2263
|
+
primary_color: z10.string().optional(),
|
|
2264
|
+
accent_color: z10.string().optional(),
|
|
2265
|
+
link_color: z10.string().optional(),
|
|
2266
|
+
background_color: z10.string().optional(),
|
|
2267
|
+
font_family: z10.string().optional(),
|
|
2268
|
+
max_width: z10.string().optional()
|
|
2269
|
+
}).optional().describe("Colors, fonts, and brand configuration"),
|
|
2270
|
+
is_default: z10.boolean().optional().describe(
|
|
2271
|
+
"Set as the default template for this customer."
|
|
2272
|
+
)
|
|
2586
2273
|
},
|
|
2587
2274
|
{
|
|
2588
|
-
title: "
|
|
2589
|
-
readOnlyHint:
|
|
2275
|
+
title: "Save Newsletter Template",
|
|
2276
|
+
readOnlyHint: false,
|
|
2590
2277
|
destructiveHint: false,
|
|
2591
|
-
idempotentHint:
|
|
2278
|
+
idempotentHint: false,
|
|
2592
2279
|
openWorldHint: false
|
|
2593
2280
|
},
|
|
2594
|
-
async ({
|
|
2281
|
+
async ({
|
|
2282
|
+
template_id,
|
|
2283
|
+
name,
|
|
2284
|
+
description,
|
|
2285
|
+
html_content,
|
|
2286
|
+
sections,
|
|
2287
|
+
style_config,
|
|
2288
|
+
is_default
|
|
2289
|
+
}) => {
|
|
2595
2290
|
try {
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
if (
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2291
|
+
const data = {
|
|
2292
|
+
name,
|
|
2293
|
+
htmlContent: html_content
|
|
2294
|
+
};
|
|
2295
|
+
if (description !== void 0) data.description = description;
|
|
2296
|
+
if (sections !== void 0) {
|
|
2297
|
+
data.sections = sections.map((s) => ({
|
|
2298
|
+
id: s.name,
|
|
2299
|
+
type: "custom",
|
|
2300
|
+
label: s.label,
|
|
2301
|
+
required: s.required ?? true,
|
|
2302
|
+
count: s.item_count
|
|
2303
|
+
}));
|
|
2304
|
+
}
|
|
2305
|
+
if (style_config !== void 0) {
|
|
2306
|
+
data.style = {
|
|
2307
|
+
primary_color: style_config.primary_color,
|
|
2308
|
+
accent_color: style_config.accent_color,
|
|
2309
|
+
link_color: style_config.link_color,
|
|
2310
|
+
background_color: style_config.background_color,
|
|
2311
|
+
font_family: style_config.font_family,
|
|
2312
|
+
content_width: style_config.max_width
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
if (is_default !== void 0) data.isDefault = is_default;
|
|
2316
|
+
let result;
|
|
2317
|
+
if (template_id) {
|
|
2318
|
+
result = await client2.updateTemplate(
|
|
2319
|
+
template_id,
|
|
2320
|
+
data
|
|
2321
|
+
);
|
|
2322
|
+
} else {
|
|
2323
|
+
result = await client2.createTemplate(data);
|
|
2324
|
+
}
|
|
2325
|
+
const action = template_id ? "updated" : "created";
|
|
2606
2326
|
return {
|
|
2607
|
-
content: [
|
|
2327
|
+
content: [
|
|
2328
|
+
{
|
|
2329
|
+
type: "text",
|
|
2330
|
+
text: `Newsletter template "${result.name}" ${action} successfully (ID: ${result.id}).${result.isDefault ? " Set as default." : ""}`
|
|
2331
|
+
}
|
|
2332
|
+
]
|
|
2608
2333
|
};
|
|
2609
2334
|
} catch (error) {
|
|
2610
2335
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2611
|
-
|
|
2336
|
+
throw new Error(`Failed to save newsletter template: ${message}`);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
);
|
|
2340
|
+
server2.tool(
|
|
2341
|
+
"list_newsletter_templates",
|
|
2342
|
+
"List all saved newsletter templates.",
|
|
2343
|
+
{},
|
|
2344
|
+
{
|
|
2345
|
+
title: "List Newsletter Templates",
|
|
2346
|
+
readOnlyHint: true,
|
|
2347
|
+
destructiveHint: false,
|
|
2348
|
+
idempotentHint: true,
|
|
2349
|
+
openWorldHint: false
|
|
2350
|
+
},
|
|
2351
|
+
async () => {
|
|
2352
|
+
try {
|
|
2353
|
+
const templates = await client2.listTemplates();
|
|
2354
|
+
if (!templates || templates.length === 0) {
|
|
2612
2355
|
return {
|
|
2613
2356
|
content: [
|
|
2614
2357
|
{
|
|
2615
2358
|
type: "text",
|
|
2616
|
-
text: "
|
|
2359
|
+
text: "No newsletter templates found. Use save_newsletter_template to create one."
|
|
2617
2360
|
}
|
|
2618
2361
|
]
|
|
2619
2362
|
};
|
|
2620
2363
|
}
|
|
2621
|
-
|
|
2364
|
+
const lines = [];
|
|
2365
|
+
lines.push(`## Newsletter Templates (${templates.length})`);
|
|
2366
|
+
lines.push("");
|
|
2367
|
+
for (const t of templates) {
|
|
2368
|
+
const badges = [];
|
|
2369
|
+
if (t.isDefault) badges.push("DEFAULT");
|
|
2370
|
+
if (t.htmlContent) badges.push("HAS HTML");
|
|
2371
|
+
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
2372
|
+
lines.push(`**${t.name}** (ID: ${t.id})${badgeStr}`);
|
|
2373
|
+
if (t.description) lines.push(` ${t.description}`);
|
|
2374
|
+
if (t.sections && t.sections.length > 0) {
|
|
2375
|
+
lines.push(
|
|
2376
|
+
` Sections: ${t.sections.map((s) => s.label).join(", ")}`
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
lines.push("");
|
|
2380
|
+
}
|
|
2381
|
+
return {
|
|
2382
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
2383
|
+
};
|
|
2384
|
+
} catch (error) {
|
|
2385
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2386
|
+
throw new Error(`Failed to list newsletter templates: ${message}`);
|
|
2622
2387
|
}
|
|
2623
2388
|
}
|
|
2624
2389
|
);
|
|
2625
2390
|
server2.tool(
|
|
2626
|
-
"
|
|
2627
|
-
"
|
|
2391
|
+
"delete_newsletter_template",
|
|
2392
|
+
"Delete a newsletter template. Set confirmed=true to proceed.",
|
|
2628
2393
|
{
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
notes: z10.string().optional().describe(
|
|
2633
|
-
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2394
|
+
template_id: z10.string().describe("The ID of the newsletter template to delete."),
|
|
2395
|
+
confirmed: z10.boolean().default(false).describe(
|
|
2396
|
+
"Must be true to actually delete. If false, returns a confirmation prompt."
|
|
2634
2397
|
)
|
|
2635
2398
|
},
|
|
2636
2399
|
{
|
|
2637
|
-
title: "
|
|
2400
|
+
title: "Delete Newsletter Template",
|
|
2638
2401
|
readOnlyHint: false,
|
|
2639
|
-
destructiveHint:
|
|
2402
|
+
destructiveHint: true,
|
|
2640
2403
|
idempotentHint: false,
|
|
2641
2404
|
openWorldHint: false
|
|
2642
2405
|
},
|
|
2643
|
-
async ({
|
|
2406
|
+
async ({ template_id, confirmed }) => {
|
|
2644
2407
|
try {
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2408
|
+
if (!confirmed) {
|
|
2409
|
+
const template = await client2.getTemplate(template_id);
|
|
2410
|
+
return {
|
|
2411
|
+
content: [
|
|
2412
|
+
{
|
|
2413
|
+
type: "text",
|
|
2414
|
+
text: `Are you sure you want to delete the template "${template.name}" (ID: ${template.id})? This action cannot be undone. Call delete_newsletter_template again with confirmed=true to proceed.`
|
|
2415
|
+
}
|
|
2416
|
+
]
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
await client2.deleteTemplate(template_id);
|
|
2652
2420
|
return {
|
|
2653
2421
|
content: [
|
|
2654
2422
|
{
|
|
2655
2423
|
type: "text",
|
|
2656
|
-
text: `Newsletter
|
|
2424
|
+
text: `Newsletter template (ID: ${template_id}) has been deleted.`
|
|
2657
2425
|
}
|
|
2658
2426
|
]
|
|
2659
2427
|
};
|
|
2660
2428
|
} catch (error) {
|
|
2661
2429
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2662
|
-
|
|
2430
|
+
if (message.includes("404")) {
|
|
2431
|
+
return {
|
|
2432
|
+
content: [
|
|
2433
|
+
{
|
|
2434
|
+
type: "text",
|
|
2435
|
+
text: "Template not found. It may have already been deleted."
|
|
2436
|
+
}
|
|
2437
|
+
]
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
throw new Error(`Failed to delete newsletter template: ${message}`);
|
|
2663
2441
|
}
|
|
2664
2442
|
}
|
|
2665
2443
|
);
|
|
@@ -2670,7 +2448,7 @@ import { z as z11 } from "zod";
|
|
|
2670
2448
|
function registerCalendarTools(server2, client2) {
|
|
2671
2449
|
server2.tool(
|
|
2672
2450
|
"get_calendar",
|
|
2673
|
-
"View the
|
|
2451
|
+
"View the content calendar with all scheduled, published, and draft posts.",
|
|
2674
2452
|
{
|
|
2675
2453
|
from: z11.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
2676
2454
|
to: z11.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
@@ -2753,7 +2531,7 @@ function registerCalendarTools(server2, client2) {
|
|
|
2753
2531
|
);
|
|
2754
2532
|
server2.tool(
|
|
2755
2533
|
"get_queue",
|
|
2756
|
-
"View the
|
|
2534
|
+
"View the posting queue schedule and recurring weekly time slots.",
|
|
2757
2535
|
{},
|
|
2758
2536
|
{
|
|
2759
2537
|
title: "Get Posting Queue",
|
|
@@ -2787,7 +2565,8 @@ function registerCalendarTools(server2, client2) {
|
|
|
2787
2565
|
for (const slot of slots) {
|
|
2788
2566
|
const day = dayNames[slot.dayOfWeek] ?? `Day ${slot.dayOfWeek}`;
|
|
2789
2567
|
const platforms = (slot.platforms ?? []).map((p) => `[${p}]`).join(" ");
|
|
2790
|
-
|
|
2568
|
+
const slotId = slot.id ?? slot._id ?? `${slot.dayOfWeek}_${slot.time}`;
|
|
2569
|
+
text += `${day}: ${slot.time} ${platforms} (id: ${slotId})
|
|
2791
2570
|
`;
|
|
2792
2571
|
}
|
|
2793
2572
|
}
|
|
@@ -2808,17 +2587,7 @@ Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
|
2808
2587
|
);
|
|
2809
2588
|
server2.tool(
|
|
2810
2589
|
"schedule_to_queue",
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
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.
|
|
2814
|
-
|
|
2815
|
-
REQUIRED WORKFLOW:
|
|
2816
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
2817
|
-
2. ALWAYS set confirmed=false first to preview
|
|
2818
|
-
3. Show the user a visual preview before confirming
|
|
2819
|
-
4. Tell the user which queue slot the post will be assigned to
|
|
2820
|
-
|
|
2821
|
-
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.",
|
|
2822
2591
|
{
|
|
2823
2592
|
content: z11.string().describe("The text content of the post"),
|
|
2824
2593
|
platforms: z11.array(z11.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -2919,7 +2688,7 @@ function timeAgo(dateStr) {
|
|
|
2919
2688
|
function registerNotificationTools(server2, client2) {
|
|
2920
2689
|
server2.tool(
|
|
2921
2690
|
"get_notifications",
|
|
2922
|
-
"Get recent notifications including post
|
|
2691
|
+
"Get recent notifications including post results, account warnings, and errors.",
|
|
2923
2692
|
{
|
|
2924
2693
|
unread_only: z12.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
2925
2694
|
limit: z12.number().optional().describe("Max notifications to return. Default 10.")
|
|
@@ -2964,7 +2733,7 @@ No notifications found. All clear!`
|
|
|
2964
2733
|
const action = n.actionLabel || "View";
|
|
2965
2734
|
if (n.resourceType === "post" && n.resourceId) {
|
|
2966
2735
|
line += `
|
|
2967
|
-
Action: ${action} \u2192
|
|
2736
|
+
Action: ${action} \u2192 get_post with post_id "${n.resourceId}"`;
|
|
2968
2737
|
} else {
|
|
2969
2738
|
line += `
|
|
2970
2739
|
Action: ${action} \u2192 ${n.actionUrl}`;
|
|
@@ -2989,10 +2758,36 @@ Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" :
|
|
|
2989
2758
|
}
|
|
2990
2759
|
|
|
2991
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
|
+
};
|
|
2992
2787
|
function registerPublishingRulesTools(server2, client2) {
|
|
2993
2788
|
server2.tool(
|
|
2994
2789
|
"get_publishing_rules",
|
|
2995
|
-
|
|
2790
|
+
"Get publishing rules and the content creation workflow. Call this BEFORE publishing, scheduling, or sending any content.",
|
|
2996
2791
|
{},
|
|
2997
2792
|
{
|
|
2998
2793
|
title: "Get Publishing Rules",
|
|
@@ -3003,90 +2798,30 @@ function registerPublishingRulesTools(server2, client2) {
|
|
|
3003
2798
|
},
|
|
3004
2799
|
async () => {
|
|
3005
2800
|
const rules = await client2.getPublishingRules();
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
`;
|
|
3025
|
-
if (rules.blockedWords && rules.blockedWords.length > 0) {
|
|
3026
|
-
text += `Blocked words: **${rules.blockedWords.join(", ")}**
|
|
3027
|
-
`;
|
|
3028
|
-
} else {
|
|
3029
|
-
text += "Blocked words: none\n";
|
|
3030
|
-
}
|
|
3031
|
-
if (rules.requiredDisclaimer) {
|
|
3032
|
-
text += `Required disclaimer: "${rules.requiredDisclaimer}"
|
|
3033
|
-
`;
|
|
3034
|
-
} else {
|
|
3035
|
-
text += "Required disclaimer: none\n";
|
|
3036
|
-
}
|
|
3037
|
-
return { content: [{ type: "text", text }] };
|
|
3038
|
-
}
|
|
3039
|
-
);
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
// src/tools/audit-log.ts
|
|
3043
|
-
import { z as z13 } from "zod";
|
|
3044
|
-
function registerAuditLogTools(server2, client2) {
|
|
3045
|
-
server2.tool(
|
|
3046
|
-
"get_audit_log",
|
|
3047
|
-
"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.",
|
|
3048
|
-
{
|
|
3049
|
-
limit: z13.number().optional().describe("Maximum number of entries to return (default 20, max 200)"),
|
|
3050
|
-
action: z13.string().optional().describe("Filter by action type prefix, e.g. 'post.published', 'newsletter.sent', 'post.' for all post actions"),
|
|
3051
|
-
resource_type: z13.string().optional().describe("Filter by resource type: post, newsletter, account, media, publishing_rules, brand_voice")
|
|
3052
|
-
},
|
|
3053
|
-
{
|
|
3054
|
-
title: "Get Audit Log",
|
|
3055
|
-
readOnlyHint: true,
|
|
3056
|
-
destructiveHint: false,
|
|
3057
|
-
idempotentHint: true,
|
|
3058
|
-
openWorldHint: false
|
|
3059
|
-
},
|
|
3060
|
-
async (args) => {
|
|
3061
|
-
const params = {};
|
|
3062
|
-
if (args.limit) params.limit = String(args.limit);
|
|
3063
|
-
if (args.action) params.action = args.action;
|
|
3064
|
-
if (args.resource_type) params.resource_type = args.resource_type;
|
|
3065
|
-
const data = await client2.getAuditLog(params);
|
|
3066
|
-
const entries = data?.entries ?? [];
|
|
3067
|
-
if (entries.length === 0) {
|
|
3068
|
-
return {
|
|
3069
|
-
content: [{ type: "text", text: "## Audit Log\n\nNo entries found." }]
|
|
3070
|
-
};
|
|
3071
|
-
}
|
|
3072
|
-
let text = `## Audit Log (${entries.length} of ${data?.total ?? entries.length} entries)
|
|
3073
|
-
|
|
3074
|
-
`;
|
|
3075
|
-
for (const entry of entries) {
|
|
3076
|
-
const date = entry.createdAt ? new Date(entry.createdAt).toLocaleString() : "unknown";
|
|
3077
|
-
const details = entry.details && Object.keys(entry.details).length > 0 ? ` \u2014 ${JSON.stringify(entry.details)}` : "";
|
|
3078
|
-
text += `- **${date}** | ${entry.action} | ${entry.resourceType}`;
|
|
3079
|
-
if (entry.resourceId) text += ` #${entry.resourceId}`;
|
|
3080
|
-
text += `${details}
|
|
3081
|
-
`;
|
|
3082
|
-
}
|
|
3083
|
-
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
|
+
};
|
|
3084
2819
|
}
|
|
3085
2820
|
);
|
|
3086
2821
|
}
|
|
3087
2822
|
|
|
3088
2823
|
// src/tools/sources.ts
|
|
3089
|
-
import { z as
|
|
2824
|
+
import { z as z13 } from "zod";
|
|
3090
2825
|
var TYPE_LABELS = {
|
|
3091
2826
|
feed: "RSS Feed",
|
|
3092
2827
|
website: "Website",
|
|
@@ -3108,12 +2843,12 @@ var TYPE_HINTS = {
|
|
|
3108
2843
|
function registerSourceTools(server2, client2) {
|
|
3109
2844
|
server2.tool(
|
|
3110
2845
|
"get_sources",
|
|
3111
|
-
"Get
|
|
2846
|
+
"Get saved content sources (RSS feeds, websites, YouTube channels, search topics).",
|
|
3112
2847
|
{
|
|
3113
|
-
type:
|
|
2848
|
+
type: z13.string().optional().describe(
|
|
3114
2849
|
"Optional: filter by type (feed, website, youtube, search, podcast, reddit, social)"
|
|
3115
2850
|
),
|
|
3116
|
-
category:
|
|
2851
|
+
category: z13.string().optional().describe("Optional: filter by category")
|
|
3117
2852
|
},
|
|
3118
2853
|
{
|
|
3119
2854
|
title: "Get Content Sources",
|
|
@@ -3174,13 +2909,13 @@ function registerSourceTools(server2, client2) {
|
|
|
3174
2909
|
);
|
|
3175
2910
|
server2.tool(
|
|
3176
2911
|
"add_source",
|
|
3177
|
-
"Save a new content source to
|
|
2912
|
+
"Save a new content source to monitor.",
|
|
3178
2913
|
{
|
|
3179
|
-
name:
|
|
3180
|
-
url:
|
|
3181
|
-
"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."
|
|
3182
2917
|
),
|
|
3183
|
-
type:
|
|
2918
|
+
type: z13.enum([
|
|
3184
2919
|
"feed",
|
|
3185
2920
|
"website",
|
|
3186
2921
|
"youtube",
|
|
@@ -3189,12 +2924,12 @@ function registerSourceTools(server2, client2) {
|
|
|
3189
2924
|
"reddit",
|
|
3190
2925
|
"social"
|
|
3191
2926
|
]).describe("Type of source. Determines how to fetch content from it."),
|
|
3192
|
-
category:
|
|
3193
|
-
"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')"
|
|
3194
2929
|
),
|
|
3195
|
-
tags:
|
|
3196
|
-
notes:
|
|
3197
|
-
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(
|
|
3198
2933
|
"For 'search' type only: the keyword or topic to search for"
|
|
3199
2934
|
)
|
|
3200
2935
|
},
|
|
@@ -3234,12 +2969,12 @@ function registerSourceTools(server2, client2) {
|
|
|
3234
2969
|
);
|
|
3235
2970
|
server2.tool(
|
|
3236
2971
|
"update_source",
|
|
3237
|
-
"Update a saved content source.
|
|
2972
|
+
"Update a saved content source.",
|
|
3238
2973
|
{
|
|
3239
|
-
source_id:
|
|
3240
|
-
name:
|
|
3241
|
-
url:
|
|
3242
|
-
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([
|
|
3243
2978
|
"feed",
|
|
3244
2979
|
"website",
|
|
3245
2980
|
"youtube",
|
|
@@ -3248,11 +2983,11 @@ function registerSourceTools(server2, client2) {
|
|
|
3248
2983
|
"reddit",
|
|
3249
2984
|
"social"
|
|
3250
2985
|
]).optional().describe("New source type"),
|
|
3251
|
-
category:
|
|
3252
|
-
tags:
|
|
3253
|
-
notes:
|
|
3254
|
-
searchQuery:
|
|
3255
|
-
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")
|
|
3256
2991
|
},
|
|
3257
2992
|
{
|
|
3258
2993
|
title: "Update Content Source",
|
|
@@ -3280,15 +3015,10 @@ function registerSourceTools(server2, client2) {
|
|
|
3280
3015
|
);
|
|
3281
3016
|
server2.tool(
|
|
3282
3017
|
"remove_source",
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
REQUIRED WORKFLOW:
|
|
3286
|
-
1. ALWAYS set confirmed=false first to preview which source will be deleted
|
|
3287
|
-
2. Show the user the source details before confirming
|
|
3288
|
-
3. Only confirm after explicit user approval`,
|
|
3018
|
+
"Remove a content source. Set confirmed=false to preview first.",
|
|
3289
3019
|
{
|
|
3290
|
-
source_id:
|
|
3291
|
-
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.")
|
|
3292
3022
|
},
|
|
3293
3023
|
{
|
|
3294
3024
|
title: "Remove Content Source",
|
|
@@ -3320,13 +3050,13 @@ This will permanently remove this content source. Call this tool again with conf
|
|
|
3320
3050
|
}
|
|
3321
3051
|
|
|
3322
3052
|
// src/tools/newsletter-advanced.ts
|
|
3323
|
-
import { z as
|
|
3053
|
+
import { z as z14 } from "zod";
|
|
3324
3054
|
function registerNewsletterAdvancedTools(server2, client2) {
|
|
3325
3055
|
server2.tool(
|
|
3326
3056
|
"get_subscriber_by_email",
|
|
3327
|
-
"Look up a subscriber by
|
|
3057
|
+
"Look up a subscriber by email address.",
|
|
3328
3058
|
{
|
|
3329
|
-
email:
|
|
3059
|
+
email: z14.string().describe("Email address to look up")
|
|
3330
3060
|
},
|
|
3331
3061
|
{
|
|
3332
3062
|
title: "Get Subscriber by Email",
|
|
@@ -3344,9 +3074,9 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3344
3074
|
);
|
|
3345
3075
|
server2.tool(
|
|
3346
3076
|
"list_automations",
|
|
3347
|
-
"List all email automations
|
|
3077
|
+
"List all email automations and sequences.",
|
|
3348
3078
|
{
|
|
3349
|
-
page:
|
|
3079
|
+
page: z14.string().optional().describe("Page number for pagination")
|
|
3350
3080
|
},
|
|
3351
3081
|
{
|
|
3352
3082
|
title: "List Automations",
|
|
@@ -3366,12 +3096,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3366
3096
|
);
|
|
3367
3097
|
server2.tool(
|
|
3368
3098
|
"list_segments",
|
|
3369
|
-
"List subscriber segments.
|
|
3099
|
+
"List subscriber segments.",
|
|
3370
3100
|
{
|
|
3371
|
-
page:
|
|
3372
|
-
type:
|
|
3373
|
-
status:
|
|
3374
|
-
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')")
|
|
3375
3105
|
},
|
|
3376
3106
|
{
|
|
3377
3107
|
title: "List Segments",
|
|
@@ -3394,10 +3124,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3394
3124
|
);
|
|
3395
3125
|
server2.tool(
|
|
3396
3126
|
"get_segment",
|
|
3397
|
-
"Get details
|
|
3127
|
+
"Get segment details. Set include_members=true to list members.",
|
|
3398
3128
|
{
|
|
3399
|
-
segment_id:
|
|
3400
|
-
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")
|
|
3401
3133
|
},
|
|
3402
3134
|
{
|
|
3403
3135
|
title: "Get Segment",
|
|
@@ -3409,92 +3141,49 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3409
3141
|
async (args) => {
|
|
3410
3142
|
const params = {};
|
|
3411
3143
|
if (args.expand) params.expand = args.expand;
|
|
3412
|
-
const
|
|
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
|
+
);
|
|
3413
3158
|
return {
|
|
3414
|
-
content: [{
|
|
3159
|
+
content: [{
|
|
3160
|
+
type: "text",
|
|
3161
|
+
text: JSON.stringify({ segment, members }, null, 2)
|
|
3162
|
+
}]
|
|
3415
3163
|
};
|
|
3416
3164
|
}
|
|
3417
3165
|
);
|
|
3418
3166
|
server2.tool(
|
|
3419
|
-
"
|
|
3420
|
-
"List
|
|
3421
|
-
{
|
|
3422
|
-
segment_id: z15.string().describe("The segment ID"),
|
|
3423
|
-
page: z15.string().optional().describe("Page number")
|
|
3424
|
-
},
|
|
3167
|
+
"list_custom_fields",
|
|
3168
|
+
"List all custom subscriber fields defined in your ESP.",
|
|
3169
|
+
{},
|
|
3425
3170
|
{
|
|
3426
|
-
title: "
|
|
3171
|
+
title: "List Custom Fields",
|
|
3427
3172
|
readOnlyHint: true,
|
|
3428
3173
|
destructiveHint: false,
|
|
3429
3174
|
idempotentHint: true,
|
|
3430
3175
|
openWorldHint: true
|
|
3431
3176
|
},
|
|
3432
|
-
async (
|
|
3433
|
-
const
|
|
3434
|
-
if (args.page) params.page = args.page;
|
|
3435
|
-
const result = await client2.getSegmentMembers(args.segment_id, params);
|
|
3177
|
+
async () => {
|
|
3178
|
+
const result = await client2.listCustomFields();
|
|
3436
3179
|
return {
|
|
3437
3180
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3438
3181
|
};
|
|
3439
3182
|
}
|
|
3440
3183
|
);
|
|
3441
3184
|
server2.tool(
|
|
3442
|
-
"
|
|
3443
|
-
"List all
|
|
3444
|
-
{},
|
|
3445
|
-
{
|
|
3446
|
-
title: "List Custom Fields",
|
|
3447
|
-
readOnlyHint: true,
|
|
3448
|
-
destructiveHint: false,
|
|
3449
|
-
idempotentHint: true,
|
|
3450
|
-
openWorldHint: true
|
|
3451
|
-
},
|
|
3452
|
-
async () => {
|
|
3453
|
-
const result = await client2.listCustomFields();
|
|
3454
|
-
return {
|
|
3455
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3456
|
-
};
|
|
3457
|
-
}
|
|
3458
|
-
);
|
|
3459
|
-
server2.tool(
|
|
3460
|
-
"get_referral_program",
|
|
3461
|
-
"Get your newsletter's referral program details including milestones and rewards (Beehiiv only).",
|
|
3462
|
-
{},
|
|
3463
|
-
{
|
|
3464
|
-
title: "Get Referral Program",
|
|
3465
|
-
readOnlyHint: true,
|
|
3466
|
-
destructiveHint: false,
|
|
3467
|
-
idempotentHint: true,
|
|
3468
|
-
openWorldHint: true
|
|
3469
|
-
},
|
|
3470
|
-
async () => {
|
|
3471
|
-
const result = await client2.getReferralProgram();
|
|
3472
|
-
return {
|
|
3473
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3474
|
-
};
|
|
3475
|
-
}
|
|
3476
|
-
);
|
|
3477
|
-
server2.tool(
|
|
3478
|
-
"list_tiers",
|
|
3479
|
-
"List all subscription tiers (free and premium) from your ESP (Beehiiv only).",
|
|
3480
|
-
{},
|
|
3481
|
-
{
|
|
3482
|
-
title: "List Tiers",
|
|
3483
|
-
readOnlyHint: true,
|
|
3484
|
-
destructiveHint: false,
|
|
3485
|
-
idempotentHint: true,
|
|
3486
|
-
openWorldHint: true
|
|
3487
|
-
},
|
|
3488
|
-
async () => {
|
|
3489
|
-
const result = await client2.listEspTiers();
|
|
3490
|
-
return {
|
|
3491
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3492
|
-
};
|
|
3493
|
-
}
|
|
3494
|
-
);
|
|
3495
|
-
server2.tool(
|
|
3496
|
-
"list_post_templates",
|
|
3497
|
-
"List all post/newsletter templates available in your ESP (Beehiiv only).",
|
|
3185
|
+
"list_post_templates",
|
|
3186
|
+
"List all post/newsletter templates available in your ESP (Beehiiv only).",
|
|
3498
3187
|
{},
|
|
3499
3188
|
{
|
|
3500
3189
|
title: "List Post Templates",
|
|
@@ -3512,7 +3201,7 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3512
3201
|
);
|
|
3513
3202
|
server2.tool(
|
|
3514
3203
|
"get_post_aggregate_stats",
|
|
3515
|
-
"Get aggregate
|
|
3204
|
+
"Get aggregate stats across all posts and newsletters.",
|
|
3516
3205
|
{},
|
|
3517
3206
|
{
|
|
3518
3207
|
title: "Get Post Aggregate Stats",
|
|
@@ -3528,30 +3217,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3528
3217
|
};
|
|
3529
3218
|
}
|
|
3530
3219
|
);
|
|
3531
|
-
server2.tool(
|
|
3532
|
-
"list_esp_webhooks",
|
|
3533
|
-
"List all webhooks configured in your ESP for event notifications.",
|
|
3534
|
-
{},
|
|
3535
|
-
{
|
|
3536
|
-
title: "List ESP Webhooks",
|
|
3537
|
-
readOnlyHint: true,
|
|
3538
|
-
destructiveHint: false,
|
|
3539
|
-
idempotentHint: true,
|
|
3540
|
-
openWorldHint: true
|
|
3541
|
-
},
|
|
3542
|
-
async () => {
|
|
3543
|
-
const result = await client2.listEspWebhooks();
|
|
3544
|
-
return {
|
|
3545
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3546
|
-
};
|
|
3547
|
-
}
|
|
3548
|
-
);
|
|
3549
3220
|
server2.tool(
|
|
3550
3221
|
"tag_subscriber",
|
|
3551
|
-
"Add tags to a subscriber.
|
|
3222
|
+
"Add tags to a subscriber.",
|
|
3552
3223
|
{
|
|
3553
|
-
subscriber_id:
|
|
3554
|
-
tags:
|
|
3224
|
+
subscriber_id: z14.string().describe("The subscriber/subscription ID"),
|
|
3225
|
+
tags: z14.array(z14.string()).describe("Tags to add to the subscriber")
|
|
3555
3226
|
},
|
|
3556
3227
|
{
|
|
3557
3228
|
title: "Tag Subscriber",
|
|
@@ -3569,17 +3240,12 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3569
3240
|
);
|
|
3570
3241
|
server2.tool(
|
|
3571
3242
|
"update_subscriber",
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
REQUIRED WORKFLOW:
|
|
3575
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3576
|
-
2. Show the user what will happen before confirming
|
|
3577
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3243
|
+
"Update subscriber details or custom fields. Set confirmed=false to preview first.",
|
|
3578
3244
|
{
|
|
3579
|
-
subscriber_id:
|
|
3580
|
-
tier:
|
|
3581
|
-
custom_fields:
|
|
3582
|
-
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")
|
|
3583
3249
|
},
|
|
3584
3250
|
{
|
|
3585
3251
|
title: "Update Subscriber",
|
|
@@ -3614,296 +3280,12 @@ Call again with confirmed=true to proceed.`
|
|
|
3614
3280
|
};
|
|
3615
3281
|
}
|
|
3616
3282
|
);
|
|
3617
|
-
server2.tool(
|
|
3618
|
-
"create_custom_field",
|
|
3619
|
-
`Create a new custom subscriber field. On Beehiiv: kind can be 'string', 'integer', 'boolean', 'date', or 'enum'. On Kit: all fields are string-typed (kind is ignored). Creates a permanent schema-level field that affects all subscribers.
|
|
3620
|
-
|
|
3621
|
-
REQUIRED WORKFLOW:
|
|
3622
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3623
|
-
2. Show the user what will happen before confirming
|
|
3624
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3625
|
-
{
|
|
3626
|
-
name: z15.string().describe("Field name"),
|
|
3627
|
-
kind: z15.string().describe("Field type: string, integer, boolean, date, or enum"),
|
|
3628
|
-
options: z15.array(z15.string()).optional().describe("Options for enum-type fields"),
|
|
3629
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm creation")
|
|
3630
|
-
},
|
|
3631
|
-
{
|
|
3632
|
-
title: "Create Custom Field",
|
|
3633
|
-
readOnlyHint: false,
|
|
3634
|
-
destructiveHint: false,
|
|
3635
|
-
idempotentHint: false,
|
|
3636
|
-
openWorldHint: true
|
|
3637
|
-
},
|
|
3638
|
-
async (args) => {
|
|
3639
|
-
if (args.confirmed !== true) {
|
|
3640
|
-
const optionsLine = args.options ? `
|
|
3641
|
-
**Options:** ${args.options.join(", ")}` : "";
|
|
3642
|
-
return {
|
|
3643
|
-
content: [{
|
|
3644
|
-
type: "text",
|
|
3645
|
-
text: `## Create Custom Field
|
|
3646
|
-
|
|
3647
|
-
**Name:** ${args.name}
|
|
3648
|
-
**Kind:** ${args.kind}${optionsLine}
|
|
3649
|
-
|
|
3650
|
-
This creates a permanent schema-level field visible on all subscribers. Call again with confirmed=true to proceed.`
|
|
3651
|
-
}]
|
|
3652
|
-
};
|
|
3653
|
-
}
|
|
3654
|
-
const data = { name: args.name, kind: args.kind };
|
|
3655
|
-
if (args.options) data.options = args.options;
|
|
3656
|
-
const result = await client2.createCustomField(data);
|
|
3657
|
-
return {
|
|
3658
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3659
|
-
};
|
|
3660
|
-
}
|
|
3661
|
-
);
|
|
3662
|
-
server2.tool(
|
|
3663
|
-
"update_custom_field",
|
|
3664
|
-
`Update an existing custom subscriber field's name or kind. Renaming or rekeying a field can break automations that reference it.
|
|
3665
|
-
|
|
3666
|
-
REQUIRED WORKFLOW:
|
|
3667
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3668
|
-
2. Show the user what will happen before confirming
|
|
3669
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3670
|
-
{
|
|
3671
|
-
field_id: z15.string().describe("The custom field ID"),
|
|
3672
|
-
name: z15.string().optional().describe("New field name"),
|
|
3673
|
-
kind: z15.string().optional().describe("New field type"),
|
|
3674
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm update")
|
|
3675
|
-
},
|
|
3676
|
-
{
|
|
3677
|
-
title: "Update Custom Field",
|
|
3678
|
-
readOnlyHint: false,
|
|
3679
|
-
destructiveHint: false,
|
|
3680
|
-
idempotentHint: true,
|
|
3681
|
-
openWorldHint: true
|
|
3682
|
-
},
|
|
3683
|
-
async (args) => {
|
|
3684
|
-
if (args.confirmed !== true) {
|
|
3685
|
-
const changes = [];
|
|
3686
|
-
if (args.name) changes.push(`**Name:** ${args.name}`);
|
|
3687
|
-
if (args.kind) changes.push(`**Kind:** ${args.kind}`);
|
|
3688
|
-
return {
|
|
3689
|
-
content: [{
|
|
3690
|
-
type: "text",
|
|
3691
|
-
text: `## Update Custom Field
|
|
3692
|
-
|
|
3693
|
-
**Field ID:** ${args.field_id}
|
|
3694
|
-
${changes.join("\n")}
|
|
3695
|
-
|
|
3696
|
-
Renaming or changing the type of a field can break automations that reference it. Call again with confirmed=true to proceed.`
|
|
3697
|
-
}]
|
|
3698
|
-
};
|
|
3699
|
-
}
|
|
3700
|
-
const data = {};
|
|
3701
|
-
if (args.name) data.name = args.name;
|
|
3702
|
-
if (args.kind) data.kind = args.kind;
|
|
3703
|
-
const result = await client2.updateCustomField(args.field_id, data);
|
|
3704
|
-
return {
|
|
3705
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3706
|
-
};
|
|
3707
|
-
}
|
|
3708
|
-
);
|
|
3709
|
-
server2.tool(
|
|
3710
|
-
"recalculate_segment",
|
|
3711
|
-
"Trigger a recalculation of a segment's membership. Useful after changing subscriber data or segment rules (Beehiiv only).",
|
|
3712
|
-
{
|
|
3713
|
-
segment_id: z15.string().describe("The segment ID to recalculate")
|
|
3714
|
-
},
|
|
3715
|
-
{
|
|
3716
|
-
title: "Recalculate Segment",
|
|
3717
|
-
readOnlyHint: false,
|
|
3718
|
-
destructiveHint: false,
|
|
3719
|
-
idempotentHint: true,
|
|
3720
|
-
openWorldHint: true
|
|
3721
|
-
},
|
|
3722
|
-
async (args) => {
|
|
3723
|
-
await client2.recalculateSegment(args.segment_id);
|
|
3724
|
-
return {
|
|
3725
|
-
content: [{ type: "text", text: "Segment recalculation triggered." }]
|
|
3726
|
-
};
|
|
3727
|
-
}
|
|
3728
|
-
);
|
|
3729
|
-
server2.tool(
|
|
3730
|
-
"enroll_in_automation",
|
|
3731
|
-
`Enroll a subscriber in an automation/journey. Provide either an email or subscription ID. On Kit, enrolls into a sequence.
|
|
3732
|
-
|
|
3733
|
-
REQUIRED WORKFLOW:
|
|
3734
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3735
|
-
2. Show the user what will happen before confirming
|
|
3736
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3737
|
-
{
|
|
3738
|
-
automation_id: z15.string().describe("The automation ID"),
|
|
3739
|
-
email: z15.string().optional().describe("Subscriber email to enroll"),
|
|
3740
|
-
subscription_id: z15.string().optional().describe("Subscription ID to enroll"),
|
|
3741
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm enrollment")
|
|
3742
|
-
},
|
|
3743
|
-
{
|
|
3744
|
-
title: "Enroll in Automation",
|
|
3745
|
-
readOnlyHint: false,
|
|
3746
|
-
destructiveHint: false,
|
|
3747
|
-
idempotentHint: false,
|
|
3748
|
-
openWorldHint: true
|
|
3749
|
-
},
|
|
3750
|
-
async (args) => {
|
|
3751
|
-
if (args.confirmed !== true) {
|
|
3752
|
-
const target = args.email ?? args.subscription_id ?? "unknown";
|
|
3753
|
-
return {
|
|
3754
|
-
content: [{
|
|
3755
|
-
type: "text",
|
|
3756
|
-
text: `## Enroll in Automation
|
|
3757
|
-
|
|
3758
|
-
**Automation ID:** ${args.automation_id}
|
|
3759
|
-
**Target:** ${target}
|
|
3760
|
-
|
|
3761
|
-
This will add the subscriber to the automation journey. Call again with confirmed=true to proceed.`
|
|
3762
|
-
}]
|
|
3763
|
-
};
|
|
3764
|
-
}
|
|
3765
|
-
const data = {};
|
|
3766
|
-
if (args.email) data.email = args.email;
|
|
3767
|
-
if (args.subscription_id) data.subscriptionId = args.subscription_id;
|
|
3768
|
-
await client2.enrollInAutomation(args.automation_id, data);
|
|
3769
|
-
return {
|
|
3770
|
-
content: [{ type: "text", text: "Subscriber enrolled in automation." }]
|
|
3771
|
-
};
|
|
3772
|
-
}
|
|
3773
|
-
);
|
|
3774
|
-
server2.tool(
|
|
3775
|
-
"bulk_add_subscribers",
|
|
3776
|
-
`Add multiple subscribers in a single request. Maximum 1000 subscribers per call.
|
|
3777
|
-
|
|
3778
|
-
REQUIRED WORKFLOW:
|
|
3779
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3780
|
-
2. Show the user what will happen before confirming
|
|
3781
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3782
|
-
{
|
|
3783
|
-
subscribers: z15.array(z15.object({
|
|
3784
|
-
email: z15.string().describe("Subscriber email"),
|
|
3785
|
-
data: z15.record(z15.unknown()).optional().describe("Additional subscriber data")
|
|
3786
|
-
})).describe("Array of subscribers to add"),
|
|
3787
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm bulk add")
|
|
3788
|
-
},
|
|
3789
|
-
{
|
|
3790
|
-
title: "Bulk Add Subscribers",
|
|
3791
|
-
readOnlyHint: false,
|
|
3792
|
-
destructiveHint: false,
|
|
3793
|
-
idempotentHint: false,
|
|
3794
|
-
openWorldHint: true
|
|
3795
|
-
},
|
|
3796
|
-
async (args) => {
|
|
3797
|
-
if (args.confirmed !== true) {
|
|
3798
|
-
return {
|
|
3799
|
-
content: [{
|
|
3800
|
-
type: "text",
|
|
3801
|
-
text: `## Bulk Add Subscribers
|
|
3802
|
-
|
|
3803
|
-
**Count:** ${args.subscribers.length} subscriber(s)
|
|
3804
|
-
**Sample:** ${args.subscribers.slice(0, 3).map((s) => s.email).join(", ")}${args.subscribers.length > 3 ? "..." : ""}
|
|
3805
|
-
|
|
3806
|
-
Call again with confirmed=true to proceed.`
|
|
3807
|
-
}]
|
|
3808
|
-
};
|
|
3809
|
-
}
|
|
3810
|
-
const result = await client2.bulkCreateSubscribers({ subscribers: args.subscribers });
|
|
3811
|
-
return {
|
|
3812
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3813
|
-
};
|
|
3814
|
-
}
|
|
3815
|
-
);
|
|
3816
|
-
server2.tool(
|
|
3817
|
-
"create_esp_webhook",
|
|
3818
|
-
`Create a webhook in your ESP to receive event notifications. Note: Kit only supports one event per webhook.
|
|
3819
|
-
|
|
3820
|
-
REQUIRED WORKFLOW:
|
|
3821
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3822
|
-
2. Show the user what will happen before confirming
|
|
3823
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3824
|
-
{
|
|
3825
|
-
url: z15.string().describe("Webhook URL to receive events"),
|
|
3826
|
-
event_types: z15.array(z15.string()).describe("Event types to subscribe to (e.g. 'subscription.created', 'post.sent')"),
|
|
3827
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm creation")
|
|
3828
|
-
},
|
|
3829
|
-
{
|
|
3830
|
-
title: "Create ESP Webhook",
|
|
3831
|
-
readOnlyHint: false,
|
|
3832
|
-
destructiveHint: false,
|
|
3833
|
-
idempotentHint: false,
|
|
3834
|
-
openWorldHint: true
|
|
3835
|
-
},
|
|
3836
|
-
async (args) => {
|
|
3837
|
-
if (args.confirmed !== true) {
|
|
3838
|
-
return {
|
|
3839
|
-
content: [{
|
|
3840
|
-
type: "text",
|
|
3841
|
-
text: `## Create ESP Webhook
|
|
3842
|
-
|
|
3843
|
-
**URL:** ${args.url}
|
|
3844
|
-
**Events:** ${args.event_types.join(", ")}
|
|
3845
|
-
|
|
3846
|
-
Call again with confirmed=true to create.`
|
|
3847
|
-
}]
|
|
3848
|
-
};
|
|
3849
|
-
}
|
|
3850
|
-
const result = await client2.createEspWebhook({ url: args.url, eventTypes: args.event_types });
|
|
3851
|
-
return {
|
|
3852
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3853
|
-
};
|
|
3854
|
-
}
|
|
3855
|
-
);
|
|
3856
|
-
server2.tool(
|
|
3857
|
-
"delete_broadcast",
|
|
3858
|
-
`Permanently delete a newsletter/broadcast. This action is permanent and cannot be undone.
|
|
3859
|
-
|
|
3860
|
-
REQUIRED WORKFLOW:
|
|
3861
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3862
|
-
2. Show the user what will happen before confirming
|
|
3863
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3864
|
-
{
|
|
3865
|
-
broadcast_id: z15.string().describe("The broadcast ID to delete"),
|
|
3866
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
3867
|
-
},
|
|
3868
|
-
{
|
|
3869
|
-
title: "Delete Broadcast",
|
|
3870
|
-
readOnlyHint: false,
|
|
3871
|
-
destructiveHint: true,
|
|
3872
|
-
idempotentHint: true,
|
|
3873
|
-
openWorldHint: true
|
|
3874
|
-
},
|
|
3875
|
-
async (args) => {
|
|
3876
|
-
if (args.confirmed !== true) {
|
|
3877
|
-
return {
|
|
3878
|
-
content: [{
|
|
3879
|
-
type: "text",
|
|
3880
|
-
text: `## Delete Broadcast
|
|
3881
|
-
|
|
3882
|
-
**Broadcast ID:** ${args.broadcast_id}
|
|
3883
|
-
|
|
3884
|
-
**This action is permanent and cannot be undone.**
|
|
3885
|
-
|
|
3886
|
-
Call again with confirmed=true to delete.`
|
|
3887
|
-
}]
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
await client2.deleteBroadcast(args.broadcast_id);
|
|
3891
|
-
return {
|
|
3892
|
-
content: [{ type: "text", text: "Broadcast deleted." }]
|
|
3893
|
-
};
|
|
3894
|
-
}
|
|
3895
|
-
);
|
|
3896
3283
|
server2.tool(
|
|
3897
3284
|
"delete_subscriber",
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
REQUIRED WORKFLOW:
|
|
3901
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3902
|
-
2. Show the user what will happen before confirming
|
|
3903
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3285
|
+
"Remove a subscriber. Set confirmed=false to preview first.",
|
|
3904
3286
|
{
|
|
3905
|
-
subscriber_id:
|
|
3906
|
-
confirmed:
|
|
3287
|
+
subscriber_id: z14.string().describe("The subscriber ID to delete"),
|
|
3288
|
+
confirmed: z14.boolean().default(false).describe("Set to true to confirm deletion")
|
|
3907
3289
|
},
|
|
3908
3290
|
{
|
|
3909
3291
|
title: "Delete Subscriber",
|
|
@@ -3933,347 +3315,6 @@ Call again with confirmed=true to delete.`
|
|
|
3933
3315
|
};
|
|
3934
3316
|
}
|
|
3935
3317
|
);
|
|
3936
|
-
server2.tool(
|
|
3937
|
-
"delete_segment",
|
|
3938
|
-
`Permanently delete a subscriber segment. This action is permanent and cannot be undone (Beehiiv only).
|
|
3939
|
-
|
|
3940
|
-
REQUIRED WORKFLOW:
|
|
3941
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3942
|
-
2. Show the user what will happen before confirming
|
|
3943
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3944
|
-
{
|
|
3945
|
-
segment_id: z15.string().describe("The segment ID to delete"),
|
|
3946
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
3947
|
-
},
|
|
3948
|
-
{
|
|
3949
|
-
title: "Delete Segment",
|
|
3950
|
-
readOnlyHint: false,
|
|
3951
|
-
destructiveHint: true,
|
|
3952
|
-
idempotentHint: true,
|
|
3953
|
-
openWorldHint: true
|
|
3954
|
-
},
|
|
3955
|
-
async (args) => {
|
|
3956
|
-
if (args.confirmed !== true) {
|
|
3957
|
-
return {
|
|
3958
|
-
content: [{
|
|
3959
|
-
type: "text",
|
|
3960
|
-
text: `## Delete Segment
|
|
3961
|
-
|
|
3962
|
-
**Segment ID:** ${args.segment_id}
|
|
3963
|
-
|
|
3964
|
-
**This action is permanent and cannot be undone.** Subscribers in this segment will not be deleted, but the segment grouping will be removed.
|
|
3965
|
-
|
|
3966
|
-
Call again with confirmed=true to delete.`
|
|
3967
|
-
}]
|
|
3968
|
-
};
|
|
3969
|
-
}
|
|
3970
|
-
await client2.deleteSegment(args.segment_id);
|
|
3971
|
-
return {
|
|
3972
|
-
content: [{ type: "text", text: "Segment deleted." }]
|
|
3973
|
-
};
|
|
3974
|
-
}
|
|
3975
|
-
);
|
|
3976
|
-
server2.tool(
|
|
3977
|
-
"delete_custom_field",
|
|
3978
|
-
`Permanently delete a custom subscriber field and remove it from all subscribers. This action is permanent and cannot be undone.
|
|
3979
|
-
|
|
3980
|
-
REQUIRED WORKFLOW:
|
|
3981
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
3982
|
-
2. Show the user what will happen before confirming
|
|
3983
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
3984
|
-
{
|
|
3985
|
-
field_id: z15.string().describe("The custom field ID to delete"),
|
|
3986
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
3987
|
-
},
|
|
3988
|
-
{
|
|
3989
|
-
title: "Delete Custom Field",
|
|
3990
|
-
readOnlyHint: false,
|
|
3991
|
-
destructiveHint: true,
|
|
3992
|
-
idempotentHint: true,
|
|
3993
|
-
openWorldHint: true
|
|
3994
|
-
},
|
|
3995
|
-
async (args) => {
|
|
3996
|
-
if (args.confirmed !== true) {
|
|
3997
|
-
return {
|
|
3998
|
-
content: [{
|
|
3999
|
-
type: "text",
|
|
4000
|
-
text: `## Delete Custom Field
|
|
4001
|
-
|
|
4002
|
-
**Field ID:** ${args.field_id}
|
|
4003
|
-
|
|
4004
|
-
**This action is permanent and cannot be undone.** The field and its data will be removed from all subscribers.
|
|
4005
|
-
|
|
4006
|
-
Call again with confirmed=true to delete.`
|
|
4007
|
-
}]
|
|
4008
|
-
};
|
|
4009
|
-
}
|
|
4010
|
-
await client2.deleteCustomField(args.field_id);
|
|
4011
|
-
return {
|
|
4012
|
-
content: [{ type: "text", text: "Custom field deleted." }]
|
|
4013
|
-
};
|
|
4014
|
-
}
|
|
4015
|
-
);
|
|
4016
|
-
server2.tool(
|
|
4017
|
-
"delete_esp_webhook",
|
|
4018
|
-
`Permanently delete a webhook from your ESP. This action is permanent and cannot be undone.
|
|
4019
|
-
|
|
4020
|
-
REQUIRED WORKFLOW:
|
|
4021
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
4022
|
-
2. Show the user what will happen before confirming
|
|
4023
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
4024
|
-
{
|
|
4025
|
-
webhook_id: z15.string().describe("The webhook ID to delete"),
|
|
4026
|
-
confirmed: z15.boolean().default(false).describe("Set to true to confirm deletion")
|
|
4027
|
-
},
|
|
4028
|
-
{
|
|
4029
|
-
title: "Delete ESP Webhook",
|
|
4030
|
-
readOnlyHint: false,
|
|
4031
|
-
destructiveHint: true,
|
|
4032
|
-
idempotentHint: true,
|
|
4033
|
-
openWorldHint: true
|
|
4034
|
-
},
|
|
4035
|
-
async (args) => {
|
|
4036
|
-
if (args.confirmed !== true) {
|
|
4037
|
-
return {
|
|
4038
|
-
content: [{
|
|
4039
|
-
type: "text",
|
|
4040
|
-
text: `## Delete ESP Webhook
|
|
4041
|
-
|
|
4042
|
-
**Webhook ID:** ${args.webhook_id}
|
|
4043
|
-
|
|
4044
|
-
**This action is permanent and cannot be undone.** You will stop receiving event notifications at this webhook URL.
|
|
4045
|
-
|
|
4046
|
-
Call again with confirmed=true to delete.`
|
|
4047
|
-
}]
|
|
4048
|
-
};
|
|
4049
|
-
}
|
|
4050
|
-
await client2.deleteEspWebhook(args.webhook_id);
|
|
4051
|
-
return {
|
|
4052
|
-
content: [{ type: "text", text: "Webhook deleted." }]
|
|
4053
|
-
};
|
|
4054
|
-
}
|
|
4055
|
-
);
|
|
4056
|
-
}
|
|
4057
|
-
|
|
4058
|
-
// src/tools/carousel.ts
|
|
4059
|
-
import { z as z16 } from "zod";
|
|
4060
|
-
function registerCarouselTools(server2, client2) {
|
|
4061
|
-
server2.tool(
|
|
4062
|
-
"get_carousel_template",
|
|
4063
|
-
"Get the customer's carousel template configuration. Returns brand colors, logo, CTA text, and other visual settings used to generate carousels from article URLs.",
|
|
4064
|
-
{},
|
|
4065
|
-
{
|
|
4066
|
-
title: "Get Carousel Template",
|
|
4067
|
-
readOnlyHint: true,
|
|
4068
|
-
destructiveHint: false,
|
|
4069
|
-
idempotentHint: true,
|
|
4070
|
-
openWorldHint: false
|
|
4071
|
-
},
|
|
4072
|
-
async () => {
|
|
4073
|
-
try {
|
|
4074
|
-
const template = await client2.getCarouselTemplate();
|
|
4075
|
-
const lines = [];
|
|
4076
|
-
lines.push(`## Carousel Template: ${template.name || "Not configured"}`);
|
|
4077
|
-
lines.push("");
|
|
4078
|
-
if (!template.id) {
|
|
4079
|
-
lines.push("No carousel template has been configured yet.");
|
|
4080
|
-
lines.push("");
|
|
4081
|
-
lines.push("Use `save_carousel_template` to set one up with your brand name, colors, and CTA text.");
|
|
4082
|
-
return {
|
|
4083
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4084
|
-
};
|
|
4085
|
-
}
|
|
4086
|
-
lines.push(`**Brand Name:** ${template.brandName || "\u2014"}`);
|
|
4087
|
-
if (template.logoUrl) lines.push(`**Logo:** ${template.logoUrl}`);
|
|
4088
|
-
lines.push(`**Primary Color:** ${template.colorPrimary || "#1a1a2e"}`);
|
|
4089
|
-
lines.push(`**Secondary Color:** ${template.colorSecondary || "#e94560"}`);
|
|
4090
|
-
lines.push(`**Accent Color:** ${template.colorAccent || "#ffffff"}`);
|
|
4091
|
-
lines.push(`**CTA Text:** ${template.ctaText || "Read the full story \u2192"}`);
|
|
4092
|
-
lines.push(`**Logo Placement:** ${template.logoPlacement || "top-left"}`);
|
|
4093
|
-
lines.push("");
|
|
4094
|
-
return {
|
|
4095
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4096
|
-
};
|
|
4097
|
-
} catch (error) {
|
|
4098
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4099
|
-
if (message.includes("404")) {
|
|
4100
|
-
return {
|
|
4101
|
-
content: [
|
|
4102
|
-
{
|
|
4103
|
-
type: "text",
|
|
4104
|
-
text: "No carousel template has been configured yet. Use `save_carousel_template` to set one up."
|
|
4105
|
-
}
|
|
4106
|
-
]
|
|
4107
|
-
};
|
|
4108
|
-
}
|
|
4109
|
-
throw error;
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
);
|
|
4113
|
-
server2.tool(
|
|
4114
|
-
"save_carousel_template",
|
|
4115
|
-
`Create or update the customer's carousel template. This configures the visual style for auto-generated carousels (brand colors, logo, CTA text).
|
|
4116
|
-
|
|
4117
|
-
USAGE:
|
|
4118
|
-
- Set confirmed=false first to preview, then confirmed=true after user approval
|
|
4119
|
-
- brand_name is required
|
|
4120
|
-
- All color values should be hex codes (e.g. "#1a1a2e")
|
|
4121
|
-
- logo_placement: "top-left", "top-right", or "top-center"`,
|
|
4122
|
-
{
|
|
4123
|
-
brand_name: z16.string().max(255).describe("Brand name displayed on CTA slide"),
|
|
4124
|
-
color_primary: z16.string().max(20).optional().describe('Primary background color (hex, e.g. "#1a1a2e")'),
|
|
4125
|
-
color_secondary: z16.string().max(20).optional().describe('Secondary/accent color for buttons and highlights (hex, e.g. "#e94560")'),
|
|
4126
|
-
color_accent: z16.string().max(20).optional().describe('Text color (hex, e.g. "#ffffff")'),
|
|
4127
|
-
cta_text: z16.string().max(255).optional().describe('Call-to-action text on the final slide (e.g. "Read the full story \u2192")'),
|
|
4128
|
-
logo_placement: z16.enum(["top-left", "top-right", "top-center"]).optional().describe("Where to place the logo on slides"),
|
|
4129
|
-
confirmed: z16.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
4130
|
-
},
|
|
4131
|
-
{
|
|
4132
|
-
title: "Save Carousel Template",
|
|
4133
|
-
readOnlyHint: false,
|
|
4134
|
-
destructiveHint: false,
|
|
4135
|
-
idempotentHint: false,
|
|
4136
|
-
openWorldHint: false
|
|
4137
|
-
},
|
|
4138
|
-
async (args) => {
|
|
4139
|
-
const payload = {
|
|
4140
|
-
brandName: args.brand_name
|
|
4141
|
-
};
|
|
4142
|
-
if (args.color_primary !== void 0) payload.colorPrimary = args.color_primary;
|
|
4143
|
-
if (args.color_secondary !== void 0) payload.colorSecondary = args.color_secondary;
|
|
4144
|
-
if (args.color_accent !== void 0) payload.colorAccent = args.color_accent;
|
|
4145
|
-
if (args.cta_text !== void 0) payload.ctaText = args.cta_text;
|
|
4146
|
-
if (args.logo_placement !== void 0) payload.logoPlacement = args.logo_placement;
|
|
4147
|
-
if (args.confirmed !== true) {
|
|
4148
|
-
const lines2 = [];
|
|
4149
|
-
lines2.push("## Carousel Template Preview");
|
|
4150
|
-
lines2.push("");
|
|
4151
|
-
lines2.push(`**Brand Name:** ${args.brand_name}`);
|
|
4152
|
-
if (args.color_primary) lines2.push(`**Primary Color:** ${args.color_primary}`);
|
|
4153
|
-
if (args.color_secondary) lines2.push(`**Secondary Color:** ${args.color_secondary}`);
|
|
4154
|
-
if (args.color_accent) lines2.push(`**Accent Color:** ${args.color_accent}`);
|
|
4155
|
-
if (args.cta_text) lines2.push(`**CTA Text:** ${args.cta_text}`);
|
|
4156
|
-
if (args.logo_placement) lines2.push(`**Logo Placement:** ${args.logo_placement}`);
|
|
4157
|
-
lines2.push("");
|
|
4158
|
-
lines2.push("---");
|
|
4159
|
-
lines2.push("Call this tool again with **confirmed=true** to save these settings.");
|
|
4160
|
-
return {
|
|
4161
|
-
content: [{ type: "text", text: lines2.join("\n") }]
|
|
4162
|
-
};
|
|
4163
|
-
}
|
|
4164
|
-
const result = await client2.updateCarouselTemplate(payload);
|
|
4165
|
-
const lines = [];
|
|
4166
|
-
lines.push(`Carousel template "${result.brandName || args.brand_name}" has been saved successfully.`);
|
|
4167
|
-
lines.push("");
|
|
4168
|
-
const updated = ["brand name"];
|
|
4169
|
-
if (args.color_primary) updated.push("primary color");
|
|
4170
|
-
if (args.color_secondary) updated.push("secondary color");
|
|
4171
|
-
if (args.color_accent) updated.push("accent color");
|
|
4172
|
-
if (args.cta_text) updated.push("CTA text");
|
|
4173
|
-
if (args.logo_placement) updated.push("logo placement");
|
|
4174
|
-
lines.push(`**Updated:** ${updated.join(", ")}`);
|
|
4175
|
-
return {
|
|
4176
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4177
|
-
};
|
|
4178
|
-
}
|
|
4179
|
-
);
|
|
4180
|
-
server2.tool(
|
|
4181
|
-
"generate_carousel",
|
|
4182
|
-
"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.",
|
|
4183
|
-
{
|
|
4184
|
-
url: z16.string().url().describe("The article URL to generate a carousel from")
|
|
4185
|
-
},
|
|
4186
|
-
{
|
|
4187
|
-
title: "Generate Carousel",
|
|
4188
|
-
readOnlyHint: false,
|
|
4189
|
-
destructiveHint: false,
|
|
4190
|
-
idempotentHint: false,
|
|
4191
|
-
openWorldHint: true
|
|
4192
|
-
},
|
|
4193
|
-
async (args) => {
|
|
4194
|
-
try {
|
|
4195
|
-
const result = await client2.generateCarousel({
|
|
4196
|
-
url: args.url
|
|
4197
|
-
});
|
|
4198
|
-
const lines = [];
|
|
4199
|
-
lines.push("## Carousel Generated");
|
|
4200
|
-
lines.push("");
|
|
4201
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4202
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4203
|
-
lines.push(`**Slides:** ${result.slides.length}`);
|
|
4204
|
-
lines.push("");
|
|
4205
|
-
lines.push("### Key Points");
|
|
4206
|
-
for (const point of result.article.keyPoints) {
|
|
4207
|
-
lines.push(`- ${point}`);
|
|
4208
|
-
}
|
|
4209
|
-
lines.push("");
|
|
4210
|
-
lines.push("### Slide URLs");
|
|
4211
|
-
for (const slide of result.slides) {
|
|
4212
|
-
lines.push(`${slide.slideNumber}. [${slide.type}](${slide.cdnUrl})`);
|
|
4213
|
-
}
|
|
4214
|
-
lines.push("");
|
|
4215
|
-
lines.push("These URLs can be used as `media_urls` when creating a post.");
|
|
4216
|
-
return {
|
|
4217
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4218
|
-
};
|
|
4219
|
-
} catch (error) {
|
|
4220
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4221
|
-
return {
|
|
4222
|
-
content: [
|
|
4223
|
-
{
|
|
4224
|
-
type: "text",
|
|
4225
|
-
text: `Failed to generate carousel: ${message}`
|
|
4226
|
-
}
|
|
4227
|
-
],
|
|
4228
|
-
isError: true
|
|
4229
|
-
};
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4232
|
-
);
|
|
4233
|
-
server2.tool(
|
|
4234
|
-
"preview_carousel",
|
|
4235
|
-
"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.",
|
|
4236
|
-
{
|
|
4237
|
-
url: z16.string().url().describe("The article URL to preview")
|
|
4238
|
-
},
|
|
4239
|
-
{
|
|
4240
|
-
title: "Preview Carousel",
|
|
4241
|
-
readOnlyHint: true,
|
|
4242
|
-
destructiveHint: false,
|
|
4243
|
-
idempotentHint: false,
|
|
4244
|
-
openWorldHint: true
|
|
4245
|
-
},
|
|
4246
|
-
async (args) => {
|
|
4247
|
-
try {
|
|
4248
|
-
const result = await client2.previewCarousel({
|
|
4249
|
-
url: args.url
|
|
4250
|
-
});
|
|
4251
|
-
const lines = [];
|
|
4252
|
-
lines.push("## Carousel Preview");
|
|
4253
|
-
lines.push("");
|
|
4254
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4255
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4256
|
-
lines.push("");
|
|
4257
|
-
lines.push(`**Hero Slide:** ${result.cdnUrl}`);
|
|
4258
|
-
lines.push("");
|
|
4259
|
-
lines.push("Use `generate_carousel` to create the full carousel with all slides.");
|
|
4260
|
-
return {
|
|
4261
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4262
|
-
};
|
|
4263
|
-
} catch (error) {
|
|
4264
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4265
|
-
return {
|
|
4266
|
-
content: [
|
|
4267
|
-
{
|
|
4268
|
-
type: "text",
|
|
4269
|
-
text: `Failed to preview carousel: ${message}`
|
|
4270
|
-
}
|
|
4271
|
-
],
|
|
4272
|
-
isError: true
|
|
4273
|
-
};
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4276
|
-
);
|
|
4277
3318
|
}
|
|
4278
3319
|
|
|
4279
3320
|
// src/index.ts
|
|
@@ -4289,10 +3330,15 @@ if (!apiKey) {
|
|
|
4289
3330
|
process.exit(1);
|
|
4290
3331
|
}
|
|
4291
3332
|
var client = new BuzzPosterClient({ baseUrl: apiUrl, apiKey });
|
|
4292
|
-
var server = new McpServer(
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
3333
|
+
var server = new McpServer(
|
|
3334
|
+
{
|
|
3335
|
+
name: "buzzposter",
|
|
3336
|
+
version: "0.1.0"
|
|
3337
|
+
},
|
|
3338
|
+
{
|
|
3339
|
+
instructions: `When a tool response contains \`NEWSLETTER_HTML_PREVIEW:\` followed by HTML content, immediately render that HTML as an artifact so the user sees a live visual preview of the newsletter. Do not ask the user if they want to see it -- just render it automatically.`
|
|
3340
|
+
}
|
|
3341
|
+
);
|
|
4296
3342
|
var allowDirectSend = false;
|
|
4297
3343
|
try {
|
|
4298
3344
|
const account = await client.getAccount();
|
|
@@ -4314,9 +3360,7 @@ registerNewsletterTemplateTools(server, client);
|
|
|
4314
3360
|
registerCalendarTools(server, client);
|
|
4315
3361
|
registerNotificationTools(server, client);
|
|
4316
3362
|
registerPublishingRulesTools(server, client);
|
|
4317
|
-
registerAuditLogTools(server, client);
|
|
4318
3363
|
registerSourceTools(server, client);
|
|
4319
3364
|
registerNewsletterAdvancedTools(server, client);
|
|
4320
|
-
registerCarouselTools(server, client);
|
|
4321
3365
|
var transport = new StdioServerTransport();
|
|
4322
3366
|
await server.connect(transport);
|