@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/tools.js
CHANGED
|
@@ -59,9 +59,6 @@ var BuzzPosterClient = class {
|
|
|
59
59
|
async deletePost(id) {
|
|
60
60
|
return this.request("DELETE", `/api/v1/posts/${id}`);
|
|
61
61
|
}
|
|
62
|
-
async retryPost(id) {
|
|
63
|
-
return this.request("POST", `/api/v1/posts/${id}/retry`);
|
|
64
|
-
}
|
|
65
62
|
// Analytics
|
|
66
63
|
async getAnalytics(params) {
|
|
67
64
|
return this.request("GET", "/api/v1/analytics", void 0, params);
|
|
@@ -95,32 +92,9 @@ var BuzzPosterClient = class {
|
|
|
95
92
|
});
|
|
96
93
|
}
|
|
97
94
|
// Media
|
|
98
|
-
async uploadMediaMultipart(filename, buffer, mimeType) {
|
|
99
|
-
const formData = new FormData();
|
|
100
|
-
formData.append("file", new Blob([buffer], { type: mimeType }), filename);
|
|
101
|
-
const url = new URL(`${this.baseUrl}/api/v1/media/upload`);
|
|
102
|
-
const res = await fetch(url, {
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
105
|
-
body: formData
|
|
106
|
-
});
|
|
107
|
-
if (!res.ok) {
|
|
108
|
-
const errorBody = await res.json().catch(() => ({ message: res.statusText }));
|
|
109
|
-
const message = typeof errorBody === "object" && errorBody !== null && "message" in errorBody ? String(errorBody.message) : `API error (${res.status})`;
|
|
110
|
-
throw new Error(`BuzzPoster API error (${res.status}): ${message}`);
|
|
111
|
-
}
|
|
112
|
-
const text = await res.text();
|
|
113
|
-
return text ? JSON.parse(text) : void 0;
|
|
114
|
-
}
|
|
115
95
|
async uploadFromUrl(data) {
|
|
116
96
|
return this.request("POST", "/api/v1/media/upload-from-url", data);
|
|
117
97
|
}
|
|
118
|
-
async uploadBase64(data) {
|
|
119
|
-
return this.request("POST", "/api/v1/media/upload-base64", data);
|
|
120
|
-
}
|
|
121
|
-
async getUploadUrl(data) {
|
|
122
|
-
return this.request("POST", "/api/v1/media/presign", data);
|
|
123
|
-
}
|
|
124
98
|
async listMedia() {
|
|
125
99
|
return this.request("GET", "/api/v1/media");
|
|
126
100
|
}
|
|
@@ -159,9 +133,6 @@ var BuzzPosterClient = class {
|
|
|
159
133
|
async listTags() {
|
|
160
134
|
return this.request("GET", "/api/v1/newsletters/tags");
|
|
161
135
|
}
|
|
162
|
-
async listSequences() {
|
|
163
|
-
return this.request("GET", "/api/v1/newsletters/sequences");
|
|
164
|
-
}
|
|
165
136
|
async listForms() {
|
|
166
137
|
return this.request("GET", "/api/v1/newsletters/forms");
|
|
167
138
|
}
|
|
@@ -172,36 +143,15 @@ var BuzzPosterClient = class {
|
|
|
172
143
|
async listAutomations(params) {
|
|
173
144
|
return this.request("GET", "/api/v1/newsletters/automations", void 0, params);
|
|
174
145
|
}
|
|
175
|
-
async enrollInAutomation(automationId, data) {
|
|
176
|
-
return this.request("POST", `/api/v1/newsletters/automations/${automationId}/enroll`, data);
|
|
177
|
-
}
|
|
178
146
|
async listSegments(params) {
|
|
179
147
|
return this.request("GET", "/api/v1/newsletters/segments", void 0, params);
|
|
180
148
|
}
|
|
181
149
|
async getSegment(id, params) {
|
|
182
150
|
return this.request("GET", `/api/v1/newsletters/segments/${id}`, void 0, params);
|
|
183
151
|
}
|
|
184
|
-
async getSegmentMembers(segmentId, params) {
|
|
185
|
-
return this.request("GET", `/api/v1/newsletters/segments/${segmentId}/members`, void 0, params);
|
|
186
|
-
}
|
|
187
|
-
async deleteSegment(id) {
|
|
188
|
-
return this.request("DELETE", `/api/v1/newsletters/segments/${id}`);
|
|
189
|
-
}
|
|
190
|
-
async recalculateSegment(id) {
|
|
191
|
-
return this.request("POST", `/api/v1/newsletters/segments/${id}/recalculate`);
|
|
192
|
-
}
|
|
193
152
|
async listCustomFields() {
|
|
194
153
|
return this.request("GET", "/api/v1/newsletters/custom-fields");
|
|
195
154
|
}
|
|
196
|
-
async createCustomField(data) {
|
|
197
|
-
return this.request("POST", "/api/v1/newsletters/custom-fields", data);
|
|
198
|
-
}
|
|
199
|
-
async updateCustomField(id, data) {
|
|
200
|
-
return this.request("PUT", `/api/v1/newsletters/custom-fields/${id}`, data);
|
|
201
|
-
}
|
|
202
|
-
async deleteCustomField(id) {
|
|
203
|
-
return this.request("DELETE", `/api/v1/newsletters/custom-fields/${id}`);
|
|
204
|
-
}
|
|
205
155
|
async tagSubscriber(subscriberId, data) {
|
|
206
156
|
return this.request("POST", `/api/v1/newsletters/subscribers/${subscriberId}/tags`, data);
|
|
207
157
|
}
|
|
@@ -211,33 +161,12 @@ var BuzzPosterClient = class {
|
|
|
211
161
|
async deleteSubscriber(id) {
|
|
212
162
|
return this.request("DELETE", `/api/v1/newsletters/subscribers/${id}`);
|
|
213
163
|
}
|
|
214
|
-
async bulkCreateSubscribers(data) {
|
|
215
|
-
return this.request("POST", "/api/v1/newsletters/subscribers/bulk", data);
|
|
216
|
-
}
|
|
217
|
-
async getReferralProgram() {
|
|
218
|
-
return this.request("GET", "/api/v1/newsletters/referral-program");
|
|
219
|
-
}
|
|
220
|
-
async listEspTiers() {
|
|
221
|
-
return this.request("GET", "/api/v1/newsletters/tiers");
|
|
222
|
-
}
|
|
223
164
|
async listPostTemplates() {
|
|
224
165
|
return this.request("GET", "/api/v1/newsletters/post-templates");
|
|
225
166
|
}
|
|
226
167
|
async getPostAggregateStats() {
|
|
227
168
|
return this.request("GET", "/api/v1/newsletters/broadcasts/aggregate-stats");
|
|
228
169
|
}
|
|
229
|
-
async deleteBroadcast(id) {
|
|
230
|
-
return this.request("DELETE", `/api/v1/newsletters/broadcasts/${id}`);
|
|
231
|
-
}
|
|
232
|
-
async listEspWebhooks() {
|
|
233
|
-
return this.request("GET", "/api/v1/newsletters/webhooks");
|
|
234
|
-
}
|
|
235
|
-
async createEspWebhook(data) {
|
|
236
|
-
return this.request("POST", "/api/v1/newsletters/webhooks", data);
|
|
237
|
-
}
|
|
238
|
-
async deleteEspWebhook(id) {
|
|
239
|
-
return this.request("DELETE", `/api/v1/newsletters/webhooks/${id}`);
|
|
240
|
-
}
|
|
241
170
|
// Knowledge
|
|
242
171
|
async listKnowledge(params) {
|
|
243
172
|
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
@@ -273,6 +202,18 @@ var BuzzPosterClient = class {
|
|
|
273
202
|
async listTemplates() {
|
|
274
203
|
return this.request("GET", "/api/v1/templates");
|
|
275
204
|
}
|
|
205
|
+
async createTemplate(data) {
|
|
206
|
+
return this.request("POST", "/api/v1/templates", data);
|
|
207
|
+
}
|
|
208
|
+
async updateTemplate(id, data) {
|
|
209
|
+
return this.request("PUT", "/api/v1/templates/" + id, data);
|
|
210
|
+
}
|
|
211
|
+
async deleteTemplate(id) {
|
|
212
|
+
return this.request("DELETE", "/api/v1/templates/" + id);
|
|
213
|
+
}
|
|
214
|
+
async duplicateTemplate(id) {
|
|
215
|
+
return this.request("POST", "/api/v1/templates/" + id + "/duplicate");
|
|
216
|
+
}
|
|
276
217
|
// Newsletter Archive
|
|
277
218
|
async listNewsletterArchive(params) {
|
|
278
219
|
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
@@ -344,10 +285,6 @@ var BuzzPosterClient = class {
|
|
|
344
285
|
async getPublishingRules() {
|
|
345
286
|
return this.request("GET", "/api/v1/publishing-rules");
|
|
346
287
|
}
|
|
347
|
-
// Audit Log
|
|
348
|
-
async getAuditLog(params) {
|
|
349
|
-
return this.request("GET", "/api/v1/audit-log", void 0, params);
|
|
350
|
-
}
|
|
351
288
|
// Newsletter Validation
|
|
352
289
|
async validateNewsletter(data) {
|
|
353
290
|
return this.request("POST", "/api/v1/newsletters/validate", data);
|
|
@@ -360,22 +297,6 @@ var BuzzPosterClient = class {
|
|
|
360
297
|
async testBroadcast(id, data) {
|
|
361
298
|
return this.request("POST", `/api/v1/newsletters/broadcasts/${id}/test`, data);
|
|
362
299
|
}
|
|
363
|
-
// Carousel templates
|
|
364
|
-
async getCarouselTemplate() {
|
|
365
|
-
return this.request("GET", "/api/v1/carousel-templates");
|
|
366
|
-
}
|
|
367
|
-
async updateCarouselTemplate(data) {
|
|
368
|
-
return this.request("PUT", "/api/v1/carousel-templates", data);
|
|
369
|
-
}
|
|
370
|
-
async deleteCarouselTemplate() {
|
|
371
|
-
return this.request("DELETE", "/api/v1/carousel-templates");
|
|
372
|
-
}
|
|
373
|
-
async generateCarousel(data) {
|
|
374
|
-
return this.request("POST", "/api/v1/carousel-templates/generate", data);
|
|
375
|
-
}
|
|
376
|
-
async previewCarousel(data) {
|
|
377
|
-
return this.request("POST", "/api/v1/carousel-templates/preview", data);
|
|
378
|
-
}
|
|
379
300
|
};
|
|
380
301
|
|
|
381
302
|
// src/tools/posts.ts
|
|
@@ -383,23 +304,7 @@ import { z } from "zod";
|
|
|
383
304
|
function registerPostTools(server, client) {
|
|
384
305
|
server.tool(
|
|
385
306
|
"post",
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
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.
|
|
389
|
-
|
|
390
|
-
REQUIRED WORKFLOW:
|
|
391
|
-
1. BEFORE creating any content, call get_brand_voice and get_audience to match the customer's tone
|
|
392
|
-
2. BEFORE publishing, call get_publishing_rules to check safety settings
|
|
393
|
-
3. ALWAYS set confirmed=false first to get a preview -- never set confirmed=true on the first call
|
|
394
|
-
4. Show the user a visual preview of the post before confirming
|
|
395
|
-
5. Only set confirmed=true after the user explicitly says to publish/post/send
|
|
396
|
-
6. If publishing_rules.social_default_action is 'draft', create as draft unless the user explicitly asks to publish
|
|
397
|
-
7. If publishing_rules.require_double_confirm_social is true, ask for confirmation twice before publishing
|
|
398
|
-
8. NEVER publish immediately without user confirmation, even if the user says "post this" -- always show the preview first
|
|
399
|
-
|
|
400
|
-
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.
|
|
401
|
-
|
|
402
|
-
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.`,
|
|
307
|
+
"Publish a post to one or more social platforms. Set confirmed=false to preview first.",
|
|
403
308
|
{
|
|
404
309
|
content: z.string().optional().describe("The text content of the post"),
|
|
405
310
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -491,118 +396,9 @@ Call this tool again with confirmed=true to proceed.`;
|
|
|
491
396
|
};
|
|
492
397
|
}
|
|
493
398
|
);
|
|
494
|
-
server.tool(
|
|
495
|
-
"cross_post",
|
|
496
|
-
`Post the same content to all connected platforms at once.
|
|
497
|
-
|
|
498
|
-
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.
|
|
499
|
-
|
|
500
|
-
REQUIRED WORKFLOW:
|
|
501
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
502
|
-
2. BEFORE publishing, call get_publishing_rules
|
|
503
|
-
3. ALWAYS set confirmed=false first to preview
|
|
504
|
-
4. Show the user a visual preview showing how the post will appear on each platform
|
|
505
|
-
5. This is a high-impact action (goes to ALL platforms) -- always require explicit confirmation
|
|
506
|
-
6. If publishing_rules.require_double_confirm_social is true, ask twice
|
|
507
|
-
|
|
508
|
-
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.`,
|
|
509
|
-
{
|
|
510
|
-
content: z.string().describe("The text content to post everywhere"),
|
|
511
|
-
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
512
|
-
confirmed: z.boolean().default(false).describe(
|
|
513
|
-
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
514
|
-
)
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
title: "Cross-Post to All Platforms",
|
|
518
|
-
readOnlyHint: false,
|
|
519
|
-
destructiveHint: false,
|
|
520
|
-
idempotentHint: false,
|
|
521
|
-
openWorldHint: true
|
|
522
|
-
},
|
|
523
|
-
async (args) => {
|
|
524
|
-
const accountsData = await client.listAccounts();
|
|
525
|
-
const platforms = (accountsData.accounts ?? []).map(
|
|
526
|
-
(a) => a.platform
|
|
527
|
-
);
|
|
528
|
-
if (platforms.length === 0) {
|
|
529
|
-
return {
|
|
530
|
-
content: [
|
|
531
|
-
{
|
|
532
|
-
type: "text",
|
|
533
|
-
text: "No connected social accounts found. Connect accounts first."
|
|
534
|
-
}
|
|
535
|
-
]
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
if (args.confirmed !== true) {
|
|
539
|
-
let rulesText = "";
|
|
540
|
-
try {
|
|
541
|
-
const rules = await client.getPublishingRules();
|
|
542
|
-
const blockedWords = rules.blockedWords ?? [];
|
|
543
|
-
const foundBlocked = [];
|
|
544
|
-
if (args.content && blockedWords.length > 0) {
|
|
545
|
-
const lower = args.content.toLowerCase();
|
|
546
|
-
for (const word of blockedWords) {
|
|
547
|
-
if (lower.includes(word.toLowerCase())) foundBlocked.push(word);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
rulesText = `
|
|
551
|
-
### Safety Checks
|
|
552
|
-
`;
|
|
553
|
-
rulesText += `- Blocked words: ${foundBlocked.length > 0 ? `**FAILED** (found: ${foundBlocked.join(", ")})` : "PASS"}
|
|
554
|
-
`;
|
|
555
|
-
rulesText += `- Default action: ${rules.socialDefaultAction ?? "draft"}
|
|
556
|
-
`;
|
|
557
|
-
rulesText += `- Double confirmation required: ${rules.requireDoubleConfirmSocial ? "yes" : "no"}
|
|
558
|
-
`;
|
|
559
|
-
} catch {
|
|
560
|
-
}
|
|
561
|
-
const preview = `## Cross-Post Preview
|
|
562
|
-
|
|
563
|
-
**Content:** "${args.content}"
|
|
564
|
-
**Platforms:** ${platforms.join(", ")} (${platforms.length} platforms)
|
|
565
|
-
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
566
|
-
` : "") + rulesText + `
|
|
567
|
-
**Action:** This will be published **immediately** to **all ${platforms.length} connected platforms**. This is a high-impact action.
|
|
568
|
-
|
|
569
|
-
Call this tool again with confirmed=true to proceed.`;
|
|
570
|
-
return { content: [{ type: "text", text: preview }] };
|
|
571
|
-
}
|
|
572
|
-
const body = {
|
|
573
|
-
content: args.content,
|
|
574
|
-
platforms: platforms.map((p) => ({ platform: p })),
|
|
575
|
-
publishNow: true
|
|
576
|
-
};
|
|
577
|
-
if (args.media_urls?.length) {
|
|
578
|
-
body.mediaItems = args.media_urls.map((url) => ({
|
|
579
|
-
type: url.match(/\.(mp4|mov|avi|webm)$/i) ? "video" : "image",
|
|
580
|
-
url
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
const result = await client.createPost(body);
|
|
584
|
-
return {
|
|
585
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
);
|
|
589
399
|
server.tool(
|
|
590
400
|
"schedule_post",
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
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.
|
|
594
|
-
|
|
595
|
-
REQUIRED WORKFLOW:
|
|
596
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
597
|
-
2. BEFORE scheduling, call get_publishing_rules
|
|
598
|
-
3. ALWAYS set confirmed=false first to preview
|
|
599
|
-
4. Show the user a visual preview with the scheduled date/time before confirming
|
|
600
|
-
5. Only confirm after explicit user approval
|
|
601
|
-
6. If the user hasn't specified a time, suggest one based on get_queue time slots
|
|
602
|
-
|
|
603
|
-
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.
|
|
604
|
-
|
|
605
|
-
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.`,
|
|
401
|
+
"Schedule a post for future publication. Set confirmed=false to preview first.",
|
|
606
402
|
{
|
|
607
403
|
content: z.string().optional().describe("The text content of the post"),
|
|
608
404
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -678,7 +474,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
678
474
|
);
|
|
679
475
|
server.tool(
|
|
680
476
|
"create_draft",
|
|
681
|
-
"Save a post as a draft without publishing.
|
|
477
|
+
"Save a post as a draft without publishing. No confirmation needed.",
|
|
682
478
|
{
|
|
683
479
|
content: z.string().optional().describe("The text content of the draft"),
|
|
684
480
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
@@ -721,7 +517,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
721
517
|
);
|
|
722
518
|
server.tool(
|
|
723
519
|
"list_posts",
|
|
724
|
-
"List recent posts with
|
|
520
|
+
"List recent posts with status and platform details.",
|
|
725
521
|
{
|
|
726
522
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
727
523
|
limit: z.string().optional().describe("Number of posts to return")
|
|
@@ -745,7 +541,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
745
541
|
);
|
|
746
542
|
server.tool(
|
|
747
543
|
"get_post",
|
|
748
|
-
"Get detailed information about a specific post.",
|
|
544
|
+
"Get detailed information about a specific post by ID.",
|
|
749
545
|
{
|
|
750
546
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
751
547
|
},
|
|
@@ -763,117 +559,13 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
763
559
|
};
|
|
764
560
|
}
|
|
765
561
|
);
|
|
766
|
-
server.tool(
|
|
767
|
-
"retry_post",
|
|
768
|
-
"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.",
|
|
769
|
-
{
|
|
770
|
-
post_id: z.string().describe("The ID of the post to retry"),
|
|
771
|
-
confirmed: z.boolean().default(false).describe(
|
|
772
|
-
"Set to true to confirm retry. If false/missing, shows post details first."
|
|
773
|
-
)
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
title: "Retry Failed Post",
|
|
777
|
-
readOnlyHint: false,
|
|
778
|
-
destructiveHint: false,
|
|
779
|
-
idempotentHint: true,
|
|
780
|
-
openWorldHint: true
|
|
781
|
-
},
|
|
782
|
-
async (args) => {
|
|
783
|
-
if (args.confirmed !== true) {
|
|
784
|
-
const post = await client.getPost(args.post_id);
|
|
785
|
-
const platforms = post.platforms ?? [];
|
|
786
|
-
const failed = platforms.filter((p) => p.status === "failed");
|
|
787
|
-
const published = platforms.filter((p) => p.status === "published");
|
|
788
|
-
let text = `## Retry Post Preview
|
|
789
|
-
|
|
790
|
-
`;
|
|
791
|
-
text += `**Post ID:** ${args.post_id}
|
|
792
|
-
`;
|
|
793
|
-
text += `**Content:** "${(post.content ?? "").substring(0, 100)}"
|
|
794
|
-
`;
|
|
795
|
-
text += `**Status:** ${post.status}
|
|
796
|
-
|
|
797
|
-
`;
|
|
798
|
-
if (published.length > 0) {
|
|
799
|
-
text += `**Already published on:** ${published.map((p) => p.platform).join(", ")}
|
|
800
|
-
`;
|
|
801
|
-
}
|
|
802
|
-
if (failed.length > 0) {
|
|
803
|
-
text += `**Failed on:** ${failed.map((p) => `${p.platform} (${p.error ?? "unknown error"})`).join(", ")}
|
|
804
|
-
`;
|
|
805
|
-
}
|
|
806
|
-
text += `
|
|
807
|
-
Retrying will only attempt the failed platforms. Call this tool again with confirmed=true to proceed.`;
|
|
808
|
-
return { content: [{ type: "text", text }] };
|
|
809
|
-
}
|
|
810
|
-
const result = await client.retryPost(args.post_id);
|
|
811
|
-
return {
|
|
812
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
);
|
|
816
562
|
}
|
|
817
563
|
|
|
818
564
|
// src/tools/accounts.ts
|
|
819
565
|
function registerAccountTools(server, client) {
|
|
820
|
-
server.tool(
|
|
821
|
-
"list_accounts",
|
|
822
|
-
"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.",
|
|
823
|
-
{},
|
|
824
|
-
{
|
|
825
|
-
title: "List Connected Accounts",
|
|
826
|
-
readOnlyHint: true,
|
|
827
|
-
destructiveHint: false,
|
|
828
|
-
idempotentHint: true,
|
|
829
|
-
openWorldHint: false
|
|
830
|
-
},
|
|
831
|
-
async () => {
|
|
832
|
-
const result = await client.listAccounts();
|
|
833
|
-
const accounts = result.accounts ?? [];
|
|
834
|
-
const esp = result.esp;
|
|
835
|
-
if (accounts.length === 0 && !esp) {
|
|
836
|
-
return {
|
|
837
|
-
content: [{
|
|
838
|
-
type: "text",
|
|
839
|
-
text: "## Connected Accounts\n\nNo accounts connected. Connect social media accounts and/or an email service provider via the dashboard."
|
|
840
|
-
}]
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
let text = "## Connected Accounts\n\n";
|
|
844
|
-
text += "### Social Media\n";
|
|
845
|
-
if (accounts.length > 0) {
|
|
846
|
-
for (const a of accounts) {
|
|
847
|
-
const icon = a.isActive ? "\u2705" : "\u274C";
|
|
848
|
-
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
849
|
-
const name = a.username || a.displayName || a.platform;
|
|
850
|
-
text += `${icon} **${platform}** \u2014 @${name}
|
|
851
|
-
`;
|
|
852
|
-
}
|
|
853
|
-
} else {
|
|
854
|
-
text += "No social accounts connected.\n";
|
|
855
|
-
}
|
|
856
|
-
text += "\n### Email Service Provider (ESP)\n";
|
|
857
|
-
if (esp && esp.connected) {
|
|
858
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
859
|
-
text += `\u2705 **${provider}** \u2014 Connected`;
|
|
860
|
-
if (esp.publicationId) text += ` (Publication: ${esp.publicationId})`;
|
|
861
|
-
text += "\n";
|
|
862
|
-
} else if (esp) {
|
|
863
|
-
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
864
|
-
text += `\u26A0\uFE0F **${provider}** \u2014 Provider set but API key missing
|
|
865
|
-
`;
|
|
866
|
-
} else {
|
|
867
|
-
text += "No ESP configured.\n";
|
|
868
|
-
}
|
|
869
|
-
return {
|
|
870
|
-
content: [{ type: "text", text }]
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
);
|
|
874
566
|
server.tool(
|
|
875
567
|
"check_accounts_health",
|
|
876
|
-
"Check
|
|
568
|
+
"Check health status of connected social accounts. Direct user to BuzzPoster web UI to reconnect broken accounts.",
|
|
877
569
|
{},
|
|
878
570
|
{
|
|
879
571
|
title: "Check Account Health",
|
|
@@ -926,7 +618,7 @@ import { z as z2 } from "zod";
|
|
|
926
618
|
function registerAnalyticsTools(server, client) {
|
|
927
619
|
server.tool(
|
|
928
620
|
"get_analytics",
|
|
929
|
-
"Get
|
|
621
|
+
"Get social media post performance analytics. Filter by platform and date range.",
|
|
930
622
|
{
|
|
931
623
|
platform: z2.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook"),
|
|
932
624
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
@@ -957,7 +649,7 @@ import { z as z3 } from "zod";
|
|
|
957
649
|
function registerInboxTools(server, client) {
|
|
958
650
|
server.tool(
|
|
959
651
|
"list_conversations",
|
|
960
|
-
"List DM conversations across all connected
|
|
652
|
+
"List DM conversations across all connected platforms.",
|
|
961
653
|
{
|
|
962
654
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
963
655
|
},
|
|
@@ -999,12 +691,7 @@ function registerInboxTools(server, client) {
|
|
|
999
691
|
);
|
|
1000
692
|
server.tool(
|
|
1001
693
|
"reply_to_conversation",
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
REQUIRED WORKFLOW:
|
|
1005
|
-
1. ALWAYS set confirmed=false first to preview the reply
|
|
1006
|
-
2. Show the user the reply text and which platform/conversation it will be sent to
|
|
1007
|
-
3. Only confirm after explicit user approval`,
|
|
694
|
+
"Send a DM reply. Set confirmed=false to preview first.",
|
|
1008
695
|
{
|
|
1009
696
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
1010
697
|
message: z3.string().describe("The reply message text"),
|
|
@@ -1060,12 +747,7 @@ This will send a DM reply on the external platform. Call this tool again with co
|
|
|
1060
747
|
);
|
|
1061
748
|
server.tool(
|
|
1062
749
|
"reply_to_comment",
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
REQUIRED WORKFLOW:
|
|
1066
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1067
|
-
2. Show the user the reply and the original comment for context
|
|
1068
|
-
3. Only confirm after explicit user approval -- public replies are visible to everyone`,
|
|
750
|
+
"Reply to a comment publicly. Set confirmed=false to preview first.",
|
|
1069
751
|
{
|
|
1070
752
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
1071
753
|
message: z3.string().describe("The reply text"),
|
|
@@ -1117,12 +799,7 @@ This will post a public reply. Call this tool again with confirmed=true to send.
|
|
|
1117
799
|
);
|
|
1118
800
|
server.tool(
|
|
1119
801
|
"reply_to_review",
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
REQUIRED WORKFLOW:
|
|
1123
|
-
1. ALWAYS set confirmed=false first to preview
|
|
1124
|
-
2. Show the user the reply, the original review, and the star rating for context
|
|
1125
|
-
3. Only confirm after explicit user approval -- public replies represent the brand`,
|
|
802
|
+
"Reply to a Facebook review publicly. Set confirmed=false to preview first.",
|
|
1126
803
|
{
|
|
1127
804
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
1128
805
|
message: z3.string().describe("The reply text"),
|
|
@@ -1158,75 +835,10 @@ This will post a public reply to the review. Call this tool again with confirmed
|
|
|
1158
835
|
|
|
1159
836
|
// src/tools/media.ts
|
|
1160
837
|
import { z as z4 } from "zod";
|
|
1161
|
-
import { readFile } from "fs/promises";
|
|
1162
838
|
function registerMediaTools(server, client) {
|
|
1163
|
-
server.tool(
|
|
1164
|
-
"upload_media",
|
|
1165
|
-
"Upload an image or video file from a local file path. Returns a CDN URL that can be used in posts.",
|
|
1166
|
-
{
|
|
1167
|
-
file_path: z4.string().describe("Absolute path to the file on the local filesystem")
|
|
1168
|
-
},
|
|
1169
|
-
{
|
|
1170
|
-
title: "Upload Media File",
|
|
1171
|
-
readOnlyHint: false,
|
|
1172
|
-
destructiveHint: false,
|
|
1173
|
-
idempotentHint: false,
|
|
1174
|
-
openWorldHint: false
|
|
1175
|
-
},
|
|
1176
|
-
async (args) => {
|
|
1177
|
-
const buffer = Buffer.from(await readFile(args.file_path));
|
|
1178
|
-
const filename = args.file_path.split("/").pop() ?? "upload";
|
|
1179
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
1180
|
-
const mimeMap = {
|
|
1181
|
-
jpg: "image/jpeg",
|
|
1182
|
-
jpeg: "image/jpeg",
|
|
1183
|
-
png: "image/png",
|
|
1184
|
-
gif: "image/gif",
|
|
1185
|
-
webp: "image/webp",
|
|
1186
|
-
mp4: "video/mp4",
|
|
1187
|
-
mov: "video/quicktime",
|
|
1188
|
-
avi: "video/x-msvideo",
|
|
1189
|
-
webm: "video/webm",
|
|
1190
|
-
pdf: "application/pdf"
|
|
1191
|
-
};
|
|
1192
|
-
const mimeType = mimeMap[ext] ?? "application/octet-stream";
|
|
1193
|
-
const result = await client.uploadMediaMultipart(filename, buffer, mimeType);
|
|
1194
|
-
return {
|
|
1195
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
);
|
|
1199
|
-
server.tool(
|
|
1200
|
-
"upload_from_claude",
|
|
1201
|
-
"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.",
|
|
1202
|
-
{
|
|
1203
|
-
data: z4.string().describe("Base64-encoded image data (no data URI prefix needed, just the raw base64 string)"),
|
|
1204
|
-
filename: z4.string().describe("Filename including extension (e.g. photo.jpg)"),
|
|
1205
|
-
content_type: z4.string().describe("MIME type (e.g. image/jpeg, image/png, image/webp, image/gif)"),
|
|
1206
|
-
folder: z4.string().optional().describe("Optional folder path within the customer's storage")
|
|
1207
|
-
},
|
|
1208
|
-
{
|
|
1209
|
-
title: "Upload Image from Claude",
|
|
1210
|
-
readOnlyHint: false,
|
|
1211
|
-
destructiveHint: false,
|
|
1212
|
-
idempotentHint: false,
|
|
1213
|
-
openWorldHint: false
|
|
1214
|
-
},
|
|
1215
|
-
async (args) => {
|
|
1216
|
-
const result = await client.uploadBase64({
|
|
1217
|
-
data: args.data,
|
|
1218
|
-
filename: args.filename,
|
|
1219
|
-
content_type: args.content_type,
|
|
1220
|
-
folder: args.folder
|
|
1221
|
-
});
|
|
1222
|
-
return {
|
|
1223
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
839
|
server.tool(
|
|
1228
840
|
"upload_from_url",
|
|
1229
|
-
"Upload media from a public URL
|
|
841
|
+
"Upload media from a public URL to storage. Returns a CDN URL for use in posts.",
|
|
1230
842
|
{
|
|
1231
843
|
url: z4.string().url().describe("Public URL of the image or video to upload"),
|
|
1232
844
|
filename: z4.string().optional().describe("Optional filename override (including extension)"),
|
|
@@ -1245,38 +857,18 @@ function registerMediaTools(server, client) {
|
|
|
1245
857
|
filename: args.filename,
|
|
1246
858
|
folder: args.folder
|
|
1247
859
|
});
|
|
860
|
+
const item = result;
|
|
1248
861
|
return {
|
|
1249
|
-
content: [
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
server.tool(
|
|
1254
|
-
"get_upload_url",
|
|
1255
|
-
"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.",
|
|
1256
|
-
{
|
|
1257
|
-
filename: z4.string().describe("The filename including extension (e.g. photo.jpg)"),
|
|
1258
|
-
content_type: z4.string().describe("MIME type of the file (e.g. image/jpeg, video/mp4)")
|
|
1259
|
-
},
|
|
1260
|
-
{
|
|
1261
|
-
title: "Get Upload URL",
|
|
1262
|
-
readOnlyHint: true,
|
|
1263
|
-
destructiveHint: false,
|
|
1264
|
-
idempotentHint: false,
|
|
1265
|
-
openWorldHint: false
|
|
1266
|
-
},
|
|
1267
|
-
async (args) => {
|
|
1268
|
-
const result = await client.getUploadUrl({
|
|
1269
|
-
filename: args.filename,
|
|
1270
|
-
content_type: args.content_type
|
|
1271
|
-
});
|
|
1272
|
-
return {
|
|
1273
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
862
|
+
content: [
|
|
863
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
864
|
+
...item.type === "image" && item.url && item.mimeType && !item.mimeType.startsWith("video/") ? [{ type: "image", url: item.url, mimeType: item.mimeType }] : []
|
|
865
|
+
]
|
|
1274
866
|
};
|
|
1275
867
|
}
|
|
1276
868
|
);
|
|
1277
869
|
server.tool(
|
|
1278
870
|
"list_media",
|
|
1279
|
-
"List all uploaded media files
|
|
871
|
+
"List all uploaded media files.",
|
|
1280
872
|
{},
|
|
1281
873
|
{
|
|
1282
874
|
title: "List Media Library",
|
|
@@ -1287,19 +879,19 @@ function registerMediaTools(server, client) {
|
|
|
1287
879
|
},
|
|
1288
880
|
async () => {
|
|
1289
881
|
const result = await client.listMedia();
|
|
882
|
+
const media = Array.isArray(result) ? result : result?.media ?? [];
|
|
883
|
+
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 }));
|
|
1290
884
|
return {
|
|
1291
|
-
content: [
|
|
885
|
+
content: [
|
|
886
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
887
|
+
...imageBlocks
|
|
888
|
+
]
|
|
1292
889
|
};
|
|
1293
890
|
}
|
|
1294
891
|
);
|
|
1295
892
|
server.tool(
|
|
1296
893
|
"delete_media",
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
REQUIRED WORKFLOW:
|
|
1300
|
-
1. ALWAYS set confirmed=false first
|
|
1301
|
-
2. Show the user which file will be deleted (filename, URL, size)
|
|
1302
|
-
3. Only confirm after explicit approval`,
|
|
894
|
+
"Delete a media file. Set confirmed=false to preview first.",
|
|
1303
895
|
{
|
|
1304
896
|
key: z4.string().describe("The key/path of the media file to delete"),
|
|
1305
897
|
confirmed: z4.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
@@ -1333,7 +925,7 @@ import { z as z5 } from "zod";
|
|
|
1333
925
|
function registerNewsletterTools(server, client, options = {}) {
|
|
1334
926
|
server.tool(
|
|
1335
927
|
"list_subscribers",
|
|
1336
|
-
"List email subscribers from
|
|
928
|
+
"List email subscribers from the connected ESP.",
|
|
1337
929
|
{
|
|
1338
930
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
1339
931
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
@@ -1357,7 +949,7 @@ function registerNewsletterTools(server, client, options = {}) {
|
|
|
1357
949
|
);
|
|
1358
950
|
server.tool(
|
|
1359
951
|
"add_subscriber",
|
|
1360
|
-
"Add a new
|
|
952
|
+
"Add a new subscriber to the mailing list.",
|
|
1361
953
|
{
|
|
1362
954
|
email: z5.string().describe("Email address of the subscriber"),
|
|
1363
955
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
@@ -1383,7 +975,7 @@ function registerNewsletterTools(server, client, options = {}) {
|
|
|
1383
975
|
);
|
|
1384
976
|
server.tool(
|
|
1385
977
|
"create_newsletter",
|
|
1386
|
-
|
|
978
|
+
"Push a newsletter to the user's ESP as a draft. Show an HTML preview to the user before calling this.",
|
|
1387
979
|
{
|
|
1388
980
|
subject: z5.string().describe("Email subject line"),
|
|
1389
981
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
@@ -1403,13 +995,17 @@ function registerNewsletterTools(server, client, options = {}) {
|
|
|
1403
995
|
previewText: args.preview_text
|
|
1404
996
|
});
|
|
1405
997
|
return {
|
|
1406
|
-
content: [
|
|
998
|
+
content: [
|
|
999
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
1000
|
+
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1001
|
+
${args.content}` }] : []
|
|
1002
|
+
]
|
|
1407
1003
|
};
|
|
1408
1004
|
}
|
|
1409
1005
|
);
|
|
1410
1006
|
server.tool(
|
|
1411
1007
|
"update_newsletter",
|
|
1412
|
-
|
|
1008
|
+
"Update an existing newsletter draft.",
|
|
1413
1009
|
{
|
|
1414
1010
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to update"),
|
|
1415
1011
|
subject: z5.string().optional().describe("Updated subject line"),
|
|
@@ -1430,27 +1026,18 @@ function registerNewsletterTools(server, client, options = {}) {
|
|
|
1430
1026
|
if (args.preview_text) data.previewText = args.preview_text;
|
|
1431
1027
|
const result = await client.updateBroadcast(args.broadcast_id, data);
|
|
1432
1028
|
return {
|
|
1433
|
-
content: [
|
|
1029
|
+
content: [
|
|
1030
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
1031
|
+
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1032
|
+
${args.content}` }] : []
|
|
1033
|
+
]
|
|
1434
1034
|
};
|
|
1435
1035
|
}
|
|
1436
1036
|
);
|
|
1437
1037
|
if (options.allowDirectSend) {
|
|
1438
1038
|
server.tool(
|
|
1439
1039
|
"send_newsletter",
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
REQUIRED WORKFLOW:
|
|
1443
|
-
1. ALWAYS call get_publishing_rules first
|
|
1444
|
-
2. NEVER send without showing a full visual preview of the newsletter first
|
|
1445
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt, not a send
|
|
1446
|
-
4. Tell the user exactly how many subscribers will receive this email
|
|
1447
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1448
|
-
- First: "Are you sure you want to send this to [X] subscribers?"
|
|
1449
|
-
- Second: "This is irreversible. Type 'send' to confirm."
|
|
1450
|
-
6. If publishing_rules.allow_immediate_send is false and the user asks to send immediately, suggest scheduling instead
|
|
1451
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview and subscriber count
|
|
1452
|
-
|
|
1453
|
-
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.`,
|
|
1040
|
+
"Send a newsletter to subscribers. This action cannot be undone. Set confirmed=false to preview first.",
|
|
1454
1041
|
{
|
|
1455
1042
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1456
1043
|
confirmed: z5.boolean().default(false).describe(
|
|
@@ -1514,22 +1101,7 @@ Call this tool again with confirmed=true to send.`;
|
|
|
1514
1101
|
}
|
|
1515
1102
|
server.tool(
|
|
1516
1103
|
"schedule_newsletter",
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
REQUIRED WORKFLOW:
|
|
1520
|
-
1. ALWAYS call get_publishing_rules first
|
|
1521
|
-
2. NEVER schedule without showing a full visual preview of the newsletter first
|
|
1522
|
-
3. ALWAYS set confirmed=false first -- this returns a confirmation prompt with details, not an actual schedule
|
|
1523
|
-
4. Tell the user exactly how many subscribers will receive this email and the scheduled send time
|
|
1524
|
-
5. If publishing_rules.require_double_confirm_newsletter is true (default), require TWO explicit confirmations:
|
|
1525
|
-
- First: "Are you sure you want to schedule this for [time] to [X] subscribers?"
|
|
1526
|
-
- Second: "Confirm schedule for [time]."
|
|
1527
|
-
6. If publishing_rules.allow_immediate_send is false and the scheduled time is very soon, warn the user
|
|
1528
|
-
7. NEVER set confirmed=true without the user explicitly confirming after seeing the preview, subscriber count, and scheduled time
|
|
1529
|
-
|
|
1530
|
-
Scheduling can typically be cancelled or rescheduled later, but the user should still verify the time is correct before confirming.
|
|
1531
|
-
|
|
1532
|
-
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.`,
|
|
1104
|
+
"Schedule a newsletter for future send. Set confirmed=false to preview first.",
|
|
1533
1105
|
{
|
|
1534
1106
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to schedule"),
|
|
1535
1107
|
scheduled_for: z5.string().describe(
|
|
@@ -1605,9 +1177,13 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1605
1177
|
);
|
|
1606
1178
|
server.tool(
|
|
1607
1179
|
"list_newsletters",
|
|
1608
|
-
"List
|
|
1180
|
+
"List newsletters with status, dates, and subject. Supports filtering by status, date range, and keyword.",
|
|
1609
1181
|
{
|
|
1610
|
-
page: z5.string().optional().describe("Page number for pagination")
|
|
1182
|
+
page: z5.string().optional().describe("Page number for pagination"),
|
|
1183
|
+
status: z5.enum(["draft", "sent", "scheduled"]).optional().describe("Filter by status: draft, sent, or scheduled"),
|
|
1184
|
+
from_date: z5.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
1185
|
+
to_date: z5.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
1186
|
+
subject: z5.string().optional().describe("Keyword to search in subject lines (case-insensitive)")
|
|
1611
1187
|
},
|
|
1612
1188
|
{
|
|
1613
1189
|
title: "List Newsletters",
|
|
@@ -1619,15 +1195,37 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1619
1195
|
async (args) => {
|
|
1620
1196
|
const params = {};
|
|
1621
1197
|
if (args.page) params.page = args.page;
|
|
1198
|
+
if (args.status) params.status = args.status;
|
|
1199
|
+
if (args.from_date) params.fromDate = args.from_date;
|
|
1200
|
+
if (args.to_date) params.toDate = args.to_date;
|
|
1201
|
+
if (args.subject) params.subject = args.subject;
|
|
1622
1202
|
const result = await client.listBroadcasts(params);
|
|
1623
|
-
|
|
1624
|
-
|
|
1203
|
+
const broadcasts = result?.broadcasts ?? [];
|
|
1204
|
+
if (broadcasts.length === 0) {
|
|
1205
|
+
return {
|
|
1206
|
+
content: [{ type: "text", text: "No newsletters found matching your filters." }]
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
let text = `## Newsletters (${broadcasts.length}`;
|
|
1210
|
+
if (result?.totalCount != null) text += ` of ${result.totalCount}`;
|
|
1211
|
+
text += ")\n\n";
|
|
1212
|
+
for (const b of broadcasts) {
|
|
1213
|
+
const status = (b.status ?? "unknown").toUpperCase();
|
|
1214
|
+
const date = b.sentAt ? new Date(b.sentAt).toLocaleString() : b.createdAt ? new Date(b.createdAt).toLocaleString() : "";
|
|
1215
|
+
text += `- **[${status}]** "${b.subject ?? "(no subject)"}"
|
|
1216
|
+
`;
|
|
1217
|
+
text += ` ID: ${b.id}`;
|
|
1218
|
+
if (date) text += ` | ${b.sentAt ? "Sent" : "Created"}: ${date}`;
|
|
1219
|
+
text += "\n";
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
content: [{ type: "text", text }]
|
|
1625
1223
|
};
|
|
1626
1224
|
}
|
|
1627
1225
|
);
|
|
1628
1226
|
server.tool(
|
|
1629
1227
|
"list_tags",
|
|
1630
|
-
"List all subscriber tags
|
|
1228
|
+
"List all subscriber tags.",
|
|
1631
1229
|
{},
|
|
1632
1230
|
{
|
|
1633
1231
|
title: "List Subscriber Tags",
|
|
@@ -1643,27 +1241,9 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
1643
1241
|
};
|
|
1644
1242
|
}
|
|
1645
1243
|
);
|
|
1646
|
-
server.tool(
|
|
1647
|
-
"list_sequences",
|
|
1648
|
-
"List all email sequences/automations from your email service provider. On Beehiiv, this returns automations. For detailed automation data use list_automations instead.",
|
|
1649
|
-
{},
|
|
1650
|
-
{
|
|
1651
|
-
title: "List Email Sequences",
|
|
1652
|
-
readOnlyHint: true,
|
|
1653
|
-
destructiveHint: false,
|
|
1654
|
-
idempotentHint: true,
|
|
1655
|
-
openWorldHint: true
|
|
1656
|
-
},
|
|
1657
|
-
async () => {
|
|
1658
|
-
const result = await client.listSequences();
|
|
1659
|
-
return {
|
|
1660
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
|
-
);
|
|
1664
1244
|
server.tool(
|
|
1665
1245
|
"list_forms",
|
|
1666
|
-
"List all signup forms
|
|
1246
|
+
"List all signup forms.",
|
|
1667
1247
|
{},
|
|
1668
1248
|
{
|
|
1669
1249
|
title: "List Signup Forms",
|
|
@@ -1686,7 +1266,7 @@ import { z as z6 } from "zod";
|
|
|
1686
1266
|
function registerNewsletterAdvancedTools(server, client) {
|
|
1687
1267
|
server.tool(
|
|
1688
1268
|
"get_subscriber_by_email",
|
|
1689
|
-
"Look up a subscriber by
|
|
1269
|
+
"Look up a subscriber by email address.",
|
|
1690
1270
|
{
|
|
1691
1271
|
email: z6.string().describe("Email address to look up")
|
|
1692
1272
|
},
|
|
@@ -1706,7 +1286,7 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1706
1286
|
);
|
|
1707
1287
|
server.tool(
|
|
1708
1288
|
"list_automations",
|
|
1709
|
-
"List all email automations
|
|
1289
|
+
"List all email automations and sequences.",
|
|
1710
1290
|
{
|
|
1711
1291
|
page: z6.string().optional().describe("Page number for pagination")
|
|
1712
1292
|
},
|
|
@@ -1728,7 +1308,7 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1728
1308
|
);
|
|
1729
1309
|
server.tool(
|
|
1730
1310
|
"list_segments",
|
|
1731
|
-
"List subscriber segments.
|
|
1311
|
+
"List subscriber segments.",
|
|
1732
1312
|
{
|
|
1733
1313
|
page: z6.string().optional().describe("Page number"),
|
|
1734
1314
|
type: z6.string().optional().describe("Filter by segment type"),
|
|
@@ -1756,10 +1336,12 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1756
1336
|
);
|
|
1757
1337
|
server.tool(
|
|
1758
1338
|
"get_segment",
|
|
1759
|
-
"Get details
|
|
1339
|
+
"Get segment details. Set include_members=true to list members.",
|
|
1760
1340
|
{
|
|
1761
1341
|
segment_id: z6.string().describe("The segment ID"),
|
|
1762
|
-
expand: z6.string().optional().describe("Comma-separated expand fields")
|
|
1342
|
+
expand: z6.string().optional().describe("Comma-separated expand fields"),
|
|
1343
|
+
include_members: z6.boolean().optional().describe("Set to true to include the member list"),
|
|
1344
|
+
members_page: z6.string().optional().describe("Page number for members pagination")
|
|
1763
1345
|
},
|
|
1764
1346
|
{
|
|
1765
1347
|
title: "Get Segment",
|
|
@@ -1771,38 +1353,31 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1771
1353
|
async (args) => {
|
|
1772
1354
|
const params = {};
|
|
1773
1355
|
if (args.expand) params.expand = args.expand;
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
title: "Get Segment Members",
|
|
1789
|
-
readOnlyHint: true,
|
|
1790
|
-
destructiveHint: false,
|
|
1791
|
-
idempotentHint: true,
|
|
1792
|
-
openWorldHint: true
|
|
1793
|
-
},
|
|
1794
|
-
async (args) => {
|
|
1795
|
-
const params = {};
|
|
1796
|
-
if (args.page) params.page = args.page;
|
|
1797
|
-
const result = await client.getSegmentMembers(args.segment_id, params);
|
|
1356
|
+
const segment = await client.getSegment(args.segment_id, params);
|
|
1357
|
+
if (!args.include_members) {
|
|
1358
|
+
return {
|
|
1359
|
+
content: [{ type: "text", text: JSON.stringify(segment, null, 2) }]
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const memberParams = {};
|
|
1363
|
+
if (args.members_page) memberParams.page = args.members_page;
|
|
1364
|
+
const members = await client.request(
|
|
1365
|
+
"GET",
|
|
1366
|
+
`/api/v1/newsletters/segments/${args.segment_id}/members`,
|
|
1367
|
+
void 0,
|
|
1368
|
+
memberParams
|
|
1369
|
+
);
|
|
1798
1370
|
return {
|
|
1799
|
-
content: [{
|
|
1371
|
+
content: [{
|
|
1372
|
+
type: "text",
|
|
1373
|
+
text: JSON.stringify({ segment, members }, null, 2)
|
|
1374
|
+
}]
|
|
1800
1375
|
};
|
|
1801
1376
|
}
|
|
1802
1377
|
);
|
|
1803
1378
|
server.tool(
|
|
1804
1379
|
"list_custom_fields",
|
|
1805
|
-
"List all custom subscriber fields defined in your ESP.
|
|
1380
|
+
"List all custom subscriber fields defined in your ESP.",
|
|
1806
1381
|
{},
|
|
1807
1382
|
{
|
|
1808
1383
|
title: "List Custom Fields",
|
|
@@ -1818,42 +1393,6 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1818
1393
|
};
|
|
1819
1394
|
}
|
|
1820
1395
|
);
|
|
1821
|
-
server.tool(
|
|
1822
|
-
"get_referral_program",
|
|
1823
|
-
"Get your newsletter's referral program details including milestones and rewards (Beehiiv only).",
|
|
1824
|
-
{},
|
|
1825
|
-
{
|
|
1826
|
-
title: "Get Referral Program",
|
|
1827
|
-
readOnlyHint: true,
|
|
1828
|
-
destructiveHint: false,
|
|
1829
|
-
idempotentHint: true,
|
|
1830
|
-
openWorldHint: true
|
|
1831
|
-
},
|
|
1832
|
-
async () => {
|
|
1833
|
-
const result = await client.getReferralProgram();
|
|
1834
|
-
return {
|
|
1835
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
);
|
|
1839
|
-
server.tool(
|
|
1840
|
-
"list_tiers",
|
|
1841
|
-
"List all subscription tiers (free and premium) from your ESP (Beehiiv only).",
|
|
1842
|
-
{},
|
|
1843
|
-
{
|
|
1844
|
-
title: "List Tiers",
|
|
1845
|
-
readOnlyHint: true,
|
|
1846
|
-
destructiveHint: false,
|
|
1847
|
-
idempotentHint: true,
|
|
1848
|
-
openWorldHint: true
|
|
1849
|
-
},
|
|
1850
|
-
async () => {
|
|
1851
|
-
const result = await client.listEspTiers();
|
|
1852
|
-
return {
|
|
1853
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
);
|
|
1857
1396
|
server.tool(
|
|
1858
1397
|
"list_post_templates",
|
|
1859
1398
|
"List all post/newsletter templates available in your ESP (Beehiiv only).",
|
|
@@ -1874,7 +1413,7 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1874
1413
|
);
|
|
1875
1414
|
server.tool(
|
|
1876
1415
|
"get_post_aggregate_stats",
|
|
1877
|
-
"Get aggregate
|
|
1416
|
+
"Get aggregate stats across all posts and newsletters.",
|
|
1878
1417
|
{},
|
|
1879
1418
|
{
|
|
1880
1419
|
title: "Get Post Aggregate Stats",
|
|
@@ -1890,27 +1429,9 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1890
1429
|
};
|
|
1891
1430
|
}
|
|
1892
1431
|
);
|
|
1893
|
-
server.tool(
|
|
1894
|
-
"list_esp_webhooks",
|
|
1895
|
-
"List all webhooks configured in your ESP for event notifications.",
|
|
1896
|
-
{},
|
|
1897
|
-
{
|
|
1898
|
-
title: "List ESP Webhooks",
|
|
1899
|
-
readOnlyHint: true,
|
|
1900
|
-
destructiveHint: false,
|
|
1901
|
-
idempotentHint: true,
|
|
1902
|
-
openWorldHint: true
|
|
1903
|
-
},
|
|
1904
|
-
async () => {
|
|
1905
|
-
const result = await client.listEspWebhooks();
|
|
1906
|
-
return {
|
|
1907
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1908
|
-
};
|
|
1909
|
-
}
|
|
1910
|
-
);
|
|
1911
1432
|
server.tool(
|
|
1912
1433
|
"tag_subscriber",
|
|
1913
|
-
"Add tags to a subscriber.
|
|
1434
|
+
"Add tags to a subscriber.",
|
|
1914
1435
|
{
|
|
1915
1436
|
subscriber_id: z6.string().describe("The subscriber/subscription ID"),
|
|
1916
1437
|
tags: z6.array(z6.string()).describe("Tags to add to the subscriber")
|
|
@@ -1931,12 +1452,7 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1931
1452
|
);
|
|
1932
1453
|
server.tool(
|
|
1933
1454
|
"update_subscriber",
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
REQUIRED WORKFLOW:
|
|
1937
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
1938
|
-
2. Show the user what will happen before confirming
|
|
1939
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
1455
|
+
"Update subscriber details or custom fields. Set confirmed=false to preview first.",
|
|
1940
1456
|
{
|
|
1941
1457
|
subscriber_id: z6.string().describe("The subscriber/subscription ID"),
|
|
1942
1458
|
tier: z6.string().optional().describe("New tier for the subscriber"),
|
|
@@ -1976,293 +1492,9 @@ Call again with confirmed=true to proceed.`
|
|
|
1976
1492
|
};
|
|
1977
1493
|
}
|
|
1978
1494
|
);
|
|
1979
|
-
server.tool(
|
|
1980
|
-
"create_custom_field",
|
|
1981
|
-
`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.
|
|
1982
|
-
|
|
1983
|
-
REQUIRED WORKFLOW:
|
|
1984
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
1985
|
-
2. Show the user what will happen before confirming
|
|
1986
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
1987
|
-
{
|
|
1988
|
-
name: z6.string().describe("Field name"),
|
|
1989
|
-
kind: z6.string().describe("Field type: string, integer, boolean, date, or enum"),
|
|
1990
|
-
options: z6.array(z6.string()).optional().describe("Options for enum-type fields"),
|
|
1991
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm creation")
|
|
1992
|
-
},
|
|
1993
|
-
{
|
|
1994
|
-
title: "Create Custom Field",
|
|
1995
|
-
readOnlyHint: false,
|
|
1996
|
-
destructiveHint: false,
|
|
1997
|
-
idempotentHint: false,
|
|
1998
|
-
openWorldHint: true
|
|
1999
|
-
},
|
|
2000
|
-
async (args) => {
|
|
2001
|
-
if (args.confirmed !== true) {
|
|
2002
|
-
const optionsLine = args.options ? `
|
|
2003
|
-
**Options:** ${args.options.join(", ")}` : "";
|
|
2004
|
-
return {
|
|
2005
|
-
content: [{
|
|
2006
|
-
type: "text",
|
|
2007
|
-
text: `## Create Custom Field
|
|
2008
|
-
|
|
2009
|
-
**Name:** ${args.name}
|
|
2010
|
-
**Kind:** ${args.kind}${optionsLine}
|
|
2011
|
-
|
|
2012
|
-
This creates a permanent schema-level field visible on all subscribers. Call again with confirmed=true to proceed.`
|
|
2013
|
-
}]
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
const data = { name: args.name, kind: args.kind };
|
|
2017
|
-
if (args.options) data.options = args.options;
|
|
2018
|
-
const result = await client.createCustomField(data);
|
|
2019
|
-
return {
|
|
2020
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
);
|
|
2024
|
-
server.tool(
|
|
2025
|
-
"update_custom_field",
|
|
2026
|
-
`Update an existing custom subscriber field's name or kind. Renaming or rekeying a field can break automations that reference it.
|
|
2027
|
-
|
|
2028
|
-
REQUIRED WORKFLOW:
|
|
2029
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2030
|
-
2. Show the user what will happen before confirming
|
|
2031
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2032
|
-
{
|
|
2033
|
-
field_id: z6.string().describe("The custom field ID"),
|
|
2034
|
-
name: z6.string().optional().describe("New field name"),
|
|
2035
|
-
kind: z6.string().optional().describe("New field type"),
|
|
2036
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm update")
|
|
2037
|
-
},
|
|
2038
|
-
{
|
|
2039
|
-
title: "Update Custom Field",
|
|
2040
|
-
readOnlyHint: false,
|
|
2041
|
-
destructiveHint: false,
|
|
2042
|
-
idempotentHint: true,
|
|
2043
|
-
openWorldHint: true
|
|
2044
|
-
},
|
|
2045
|
-
async (args) => {
|
|
2046
|
-
if (args.confirmed !== true) {
|
|
2047
|
-
const changes = [];
|
|
2048
|
-
if (args.name) changes.push(`**Name:** ${args.name}`);
|
|
2049
|
-
if (args.kind) changes.push(`**Kind:** ${args.kind}`);
|
|
2050
|
-
return {
|
|
2051
|
-
content: [{
|
|
2052
|
-
type: "text",
|
|
2053
|
-
text: `## Update Custom Field
|
|
2054
|
-
|
|
2055
|
-
**Field ID:** ${args.field_id}
|
|
2056
|
-
${changes.join("\n")}
|
|
2057
|
-
|
|
2058
|
-
Renaming or changing the type of a field can break automations that reference it. Call again with confirmed=true to proceed.`
|
|
2059
|
-
}]
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
const data = {};
|
|
2063
|
-
if (args.name) data.name = args.name;
|
|
2064
|
-
if (args.kind) data.kind = args.kind;
|
|
2065
|
-
const result = await client.updateCustomField(args.field_id, data);
|
|
2066
|
-
return {
|
|
2067
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
);
|
|
2071
|
-
server.tool(
|
|
2072
|
-
"recalculate_segment",
|
|
2073
|
-
"Trigger a recalculation of a segment's membership. Useful after changing subscriber data or segment rules (Beehiiv only).",
|
|
2074
|
-
{
|
|
2075
|
-
segment_id: z6.string().describe("The segment ID to recalculate")
|
|
2076
|
-
},
|
|
2077
|
-
{
|
|
2078
|
-
title: "Recalculate Segment",
|
|
2079
|
-
readOnlyHint: false,
|
|
2080
|
-
destructiveHint: false,
|
|
2081
|
-
idempotentHint: true,
|
|
2082
|
-
openWorldHint: true
|
|
2083
|
-
},
|
|
2084
|
-
async (args) => {
|
|
2085
|
-
await client.recalculateSegment(args.segment_id);
|
|
2086
|
-
return {
|
|
2087
|
-
content: [{ type: "text", text: "Segment recalculation triggered." }]
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
);
|
|
2091
|
-
server.tool(
|
|
2092
|
-
"enroll_in_automation",
|
|
2093
|
-
`Enroll a subscriber in an automation/journey. Provide either an email or subscription ID. On Kit, enrolls into a sequence.
|
|
2094
|
-
|
|
2095
|
-
REQUIRED WORKFLOW:
|
|
2096
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2097
|
-
2. Show the user what will happen before confirming
|
|
2098
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2099
|
-
{
|
|
2100
|
-
automation_id: z6.string().describe("The automation ID"),
|
|
2101
|
-
email: z6.string().optional().describe("Subscriber email to enroll"),
|
|
2102
|
-
subscription_id: z6.string().optional().describe("Subscription ID to enroll"),
|
|
2103
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm enrollment")
|
|
2104
|
-
},
|
|
2105
|
-
{
|
|
2106
|
-
title: "Enroll in Automation",
|
|
2107
|
-
readOnlyHint: false,
|
|
2108
|
-
destructiveHint: false,
|
|
2109
|
-
idempotentHint: false,
|
|
2110
|
-
openWorldHint: true
|
|
2111
|
-
},
|
|
2112
|
-
async (args) => {
|
|
2113
|
-
if (args.confirmed !== true) {
|
|
2114
|
-
const target = args.email ?? args.subscription_id ?? "unknown";
|
|
2115
|
-
return {
|
|
2116
|
-
content: [{
|
|
2117
|
-
type: "text",
|
|
2118
|
-
text: `## Enroll in Automation
|
|
2119
|
-
|
|
2120
|
-
**Automation ID:** ${args.automation_id}
|
|
2121
|
-
**Target:** ${target}
|
|
2122
|
-
|
|
2123
|
-
This will add the subscriber to the automation journey. Call again with confirmed=true to proceed.`
|
|
2124
|
-
}]
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
const data = {};
|
|
2128
|
-
if (args.email) data.email = args.email;
|
|
2129
|
-
if (args.subscription_id) data.subscriptionId = args.subscription_id;
|
|
2130
|
-
await client.enrollInAutomation(args.automation_id, data);
|
|
2131
|
-
return {
|
|
2132
|
-
content: [{ type: "text", text: "Subscriber enrolled in automation." }]
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
|
-
);
|
|
2136
|
-
server.tool(
|
|
2137
|
-
"bulk_add_subscribers",
|
|
2138
|
-
`Add multiple subscribers in a single request. Maximum 1000 subscribers per call.
|
|
2139
|
-
|
|
2140
|
-
REQUIRED WORKFLOW:
|
|
2141
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2142
|
-
2. Show the user what will happen before confirming
|
|
2143
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2144
|
-
{
|
|
2145
|
-
subscribers: z6.array(z6.object({
|
|
2146
|
-
email: z6.string().describe("Subscriber email"),
|
|
2147
|
-
data: z6.record(z6.unknown()).optional().describe("Additional subscriber data")
|
|
2148
|
-
})).describe("Array of subscribers to add"),
|
|
2149
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm bulk add")
|
|
2150
|
-
},
|
|
2151
|
-
{
|
|
2152
|
-
title: "Bulk Add Subscribers",
|
|
2153
|
-
readOnlyHint: false,
|
|
2154
|
-
destructiveHint: false,
|
|
2155
|
-
idempotentHint: false,
|
|
2156
|
-
openWorldHint: true
|
|
2157
|
-
},
|
|
2158
|
-
async (args) => {
|
|
2159
|
-
if (args.confirmed !== true) {
|
|
2160
|
-
return {
|
|
2161
|
-
content: [{
|
|
2162
|
-
type: "text",
|
|
2163
|
-
text: `## Bulk Add Subscribers
|
|
2164
|
-
|
|
2165
|
-
**Count:** ${args.subscribers.length} subscriber(s)
|
|
2166
|
-
**Sample:** ${args.subscribers.slice(0, 3).map((s) => s.email).join(", ")}${args.subscribers.length > 3 ? "..." : ""}
|
|
2167
|
-
|
|
2168
|
-
Call again with confirmed=true to proceed.`
|
|
2169
|
-
}]
|
|
2170
|
-
};
|
|
2171
|
-
}
|
|
2172
|
-
const result = await client.bulkCreateSubscribers({ subscribers: args.subscribers });
|
|
2173
|
-
return {
|
|
2174
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2175
|
-
};
|
|
2176
|
-
}
|
|
2177
|
-
);
|
|
2178
|
-
server.tool(
|
|
2179
|
-
"create_esp_webhook",
|
|
2180
|
-
`Create a webhook in your ESP to receive event notifications. Note: Kit only supports one event per webhook.
|
|
2181
|
-
|
|
2182
|
-
REQUIRED WORKFLOW:
|
|
2183
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2184
|
-
2. Show the user what will happen before confirming
|
|
2185
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2186
|
-
{
|
|
2187
|
-
url: z6.string().describe("Webhook URL to receive events"),
|
|
2188
|
-
event_types: z6.array(z6.string()).describe("Event types to subscribe to (e.g. 'subscription.created', 'post.sent')"),
|
|
2189
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm creation")
|
|
2190
|
-
},
|
|
2191
|
-
{
|
|
2192
|
-
title: "Create ESP Webhook",
|
|
2193
|
-
readOnlyHint: false,
|
|
2194
|
-
destructiveHint: false,
|
|
2195
|
-
idempotentHint: false,
|
|
2196
|
-
openWorldHint: true
|
|
2197
|
-
},
|
|
2198
|
-
async (args) => {
|
|
2199
|
-
if (args.confirmed !== true) {
|
|
2200
|
-
return {
|
|
2201
|
-
content: [{
|
|
2202
|
-
type: "text",
|
|
2203
|
-
text: `## Create ESP Webhook
|
|
2204
|
-
|
|
2205
|
-
**URL:** ${args.url}
|
|
2206
|
-
**Events:** ${args.event_types.join(", ")}
|
|
2207
|
-
|
|
2208
|
-
Call again with confirmed=true to create.`
|
|
2209
|
-
}]
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
const result = await client.createEspWebhook({ url: args.url, eventTypes: args.event_types });
|
|
2213
|
-
return {
|
|
2214
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2215
|
-
};
|
|
2216
|
-
}
|
|
2217
|
-
);
|
|
2218
|
-
server.tool(
|
|
2219
|
-
"delete_broadcast",
|
|
2220
|
-
`Permanently delete a newsletter/broadcast. This action is permanent and cannot be undone.
|
|
2221
|
-
|
|
2222
|
-
REQUIRED WORKFLOW:
|
|
2223
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2224
|
-
2. Show the user what will happen before confirming
|
|
2225
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2226
|
-
{
|
|
2227
|
-
broadcast_id: z6.string().describe("The broadcast ID to delete"),
|
|
2228
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm deletion")
|
|
2229
|
-
},
|
|
2230
|
-
{
|
|
2231
|
-
title: "Delete Broadcast",
|
|
2232
|
-
readOnlyHint: false,
|
|
2233
|
-
destructiveHint: true,
|
|
2234
|
-
idempotentHint: true,
|
|
2235
|
-
openWorldHint: true
|
|
2236
|
-
},
|
|
2237
|
-
async (args) => {
|
|
2238
|
-
if (args.confirmed !== true) {
|
|
2239
|
-
return {
|
|
2240
|
-
content: [{
|
|
2241
|
-
type: "text",
|
|
2242
|
-
text: `## Delete Broadcast
|
|
2243
|
-
|
|
2244
|
-
**Broadcast ID:** ${args.broadcast_id}
|
|
2245
|
-
|
|
2246
|
-
**This action is permanent and cannot be undone.**
|
|
2247
|
-
|
|
2248
|
-
Call again with confirmed=true to delete.`
|
|
2249
|
-
}]
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
await client.deleteBroadcast(args.broadcast_id);
|
|
2253
|
-
return {
|
|
2254
|
-
content: [{ type: "text", text: "Broadcast deleted." }]
|
|
2255
|
-
};
|
|
2256
|
-
}
|
|
2257
|
-
);
|
|
2258
1495
|
server.tool(
|
|
2259
1496
|
"delete_subscriber",
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
REQUIRED WORKFLOW:
|
|
2263
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2264
|
-
2. Show the user what will happen before confirming
|
|
2265
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
1497
|
+
"Remove a subscriber. Set confirmed=false to preview first.",
|
|
2266
1498
|
{
|
|
2267
1499
|
subscriber_id: z6.string().describe("The subscriber ID to delete"),
|
|
2268
1500
|
confirmed: z6.boolean().default(false).describe("Set to true to confirm deletion")
|
|
@@ -2295,126 +1527,6 @@ Call again with confirmed=true to delete.`
|
|
|
2295
1527
|
};
|
|
2296
1528
|
}
|
|
2297
1529
|
);
|
|
2298
|
-
server.tool(
|
|
2299
|
-
"delete_segment",
|
|
2300
|
-
`Permanently delete a subscriber segment. This action is permanent and cannot be undone (Beehiiv only).
|
|
2301
|
-
|
|
2302
|
-
REQUIRED WORKFLOW:
|
|
2303
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2304
|
-
2. Show the user what will happen before confirming
|
|
2305
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2306
|
-
{
|
|
2307
|
-
segment_id: z6.string().describe("The segment ID to delete"),
|
|
2308
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm deletion")
|
|
2309
|
-
},
|
|
2310
|
-
{
|
|
2311
|
-
title: "Delete Segment",
|
|
2312
|
-
readOnlyHint: false,
|
|
2313
|
-
destructiveHint: true,
|
|
2314
|
-
idempotentHint: true,
|
|
2315
|
-
openWorldHint: true
|
|
2316
|
-
},
|
|
2317
|
-
async (args) => {
|
|
2318
|
-
if (args.confirmed !== true) {
|
|
2319
|
-
return {
|
|
2320
|
-
content: [{
|
|
2321
|
-
type: "text",
|
|
2322
|
-
text: `## Delete Segment
|
|
2323
|
-
|
|
2324
|
-
**Segment ID:** ${args.segment_id}
|
|
2325
|
-
|
|
2326
|
-
**This action is permanent and cannot be undone.** Subscribers in this segment will not be deleted, but the segment grouping will be removed.
|
|
2327
|
-
|
|
2328
|
-
Call again with confirmed=true to delete.`
|
|
2329
|
-
}]
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
await client.deleteSegment(args.segment_id);
|
|
2333
|
-
return {
|
|
2334
|
-
content: [{ type: "text", text: "Segment deleted." }]
|
|
2335
|
-
};
|
|
2336
|
-
}
|
|
2337
|
-
);
|
|
2338
|
-
server.tool(
|
|
2339
|
-
"delete_custom_field",
|
|
2340
|
-
`Permanently delete a custom subscriber field and remove it from all subscribers. This action is permanent and cannot be undone.
|
|
2341
|
-
|
|
2342
|
-
REQUIRED WORKFLOW:
|
|
2343
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2344
|
-
2. Show the user what will happen before confirming
|
|
2345
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2346
|
-
{
|
|
2347
|
-
field_id: z6.string().describe("The custom field ID to delete"),
|
|
2348
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm deletion")
|
|
2349
|
-
},
|
|
2350
|
-
{
|
|
2351
|
-
title: "Delete Custom Field",
|
|
2352
|
-
readOnlyHint: false,
|
|
2353
|
-
destructiveHint: true,
|
|
2354
|
-
idempotentHint: true,
|
|
2355
|
-
openWorldHint: true
|
|
2356
|
-
},
|
|
2357
|
-
async (args) => {
|
|
2358
|
-
if (args.confirmed !== true) {
|
|
2359
|
-
return {
|
|
2360
|
-
content: [{
|
|
2361
|
-
type: "text",
|
|
2362
|
-
text: `## Delete Custom Field
|
|
2363
|
-
|
|
2364
|
-
**Field ID:** ${args.field_id}
|
|
2365
|
-
|
|
2366
|
-
**This action is permanent and cannot be undone.** The field and its data will be removed from all subscribers.
|
|
2367
|
-
|
|
2368
|
-
Call again with confirmed=true to delete.`
|
|
2369
|
-
}]
|
|
2370
|
-
};
|
|
2371
|
-
}
|
|
2372
|
-
await client.deleteCustomField(args.field_id);
|
|
2373
|
-
return {
|
|
2374
|
-
content: [{ type: "text", text: "Custom field deleted." }]
|
|
2375
|
-
};
|
|
2376
|
-
}
|
|
2377
|
-
);
|
|
2378
|
-
server.tool(
|
|
2379
|
-
"delete_esp_webhook",
|
|
2380
|
-
`Permanently delete a webhook from your ESP. This action is permanent and cannot be undone.
|
|
2381
|
-
|
|
2382
|
-
REQUIRED WORKFLOW:
|
|
2383
|
-
1. ALWAYS set confirmed=false first to get a preview
|
|
2384
|
-
2. Show the user what will happen before confirming
|
|
2385
|
-
3. Only set confirmed=true after the user explicitly approves`,
|
|
2386
|
-
{
|
|
2387
|
-
webhook_id: z6.string().describe("The webhook ID to delete"),
|
|
2388
|
-
confirmed: z6.boolean().default(false).describe("Set to true to confirm deletion")
|
|
2389
|
-
},
|
|
2390
|
-
{
|
|
2391
|
-
title: "Delete ESP Webhook",
|
|
2392
|
-
readOnlyHint: false,
|
|
2393
|
-
destructiveHint: true,
|
|
2394
|
-
idempotentHint: true,
|
|
2395
|
-
openWorldHint: true
|
|
2396
|
-
},
|
|
2397
|
-
async (args) => {
|
|
2398
|
-
if (args.confirmed !== true) {
|
|
2399
|
-
return {
|
|
2400
|
-
content: [{
|
|
2401
|
-
type: "text",
|
|
2402
|
-
text: `## Delete ESP Webhook
|
|
2403
|
-
|
|
2404
|
-
**Webhook ID:** ${args.webhook_id}
|
|
2405
|
-
|
|
2406
|
-
**This action is permanent and cannot be undone.** You will stop receiving event notifications at this webhook URL.
|
|
2407
|
-
|
|
2408
|
-
Call again with confirmed=true to delete.`
|
|
2409
|
-
}]
|
|
2410
|
-
};
|
|
2411
|
-
}
|
|
2412
|
-
await client.deleteEspWebhook(args.webhook_id);
|
|
2413
|
-
return {
|
|
2414
|
-
content: [{ type: "text", text: "Webhook deleted." }]
|
|
2415
|
-
};
|
|
2416
|
-
}
|
|
2417
|
-
);
|
|
2418
1530
|
}
|
|
2419
1531
|
|
|
2420
1532
|
// src/tools/rss.ts
|
|
@@ -2422,7 +1534,7 @@ import { z as z7 } from "zod";
|
|
|
2422
1534
|
function registerRssTools(server, client) {
|
|
2423
1535
|
server.tool(
|
|
2424
1536
|
"fetch_feed",
|
|
2425
|
-
"Fetch and parse entries from an RSS or Atom feed URL.
|
|
1537
|
+
"Fetch and parse entries from an RSS or Atom feed URL.",
|
|
2426
1538
|
{
|
|
2427
1539
|
url: z7.string().describe("The RSS/Atom feed URL to fetch"),
|
|
2428
1540
|
limit: z7.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
@@ -2443,7 +1555,7 @@ function registerRssTools(server, client) {
|
|
|
2443
1555
|
);
|
|
2444
1556
|
server.tool(
|
|
2445
1557
|
"fetch_article",
|
|
2446
|
-
"Extract
|
|
1558
|
+
"Extract full article content from a URL as clean text.",
|
|
2447
1559
|
{
|
|
2448
1560
|
url: z7.string().describe("The article URL to extract content from")
|
|
2449
1561
|
},
|
|
@@ -2467,7 +1579,7 @@ function registerRssTools(server, client) {
|
|
|
2467
1579
|
function registerAccountInfoTool(server, client) {
|
|
2468
1580
|
server.tool(
|
|
2469
1581
|
"get_account",
|
|
2470
|
-
"Get
|
|
1582
|
+
"Get account details, subscription status, ESP config, and all connected social/ESP accounts.",
|
|
2471
1583
|
{},
|
|
2472
1584
|
{
|
|
2473
1585
|
title: "Get Account Details",
|
|
@@ -2477,9 +1589,15 @@ function registerAccountInfoTool(server, client) {
|
|
|
2477
1589
|
openWorldHint: false
|
|
2478
1590
|
},
|
|
2479
1591
|
async () => {
|
|
2480
|
-
const
|
|
1592
|
+
const [account, accountsData] = await Promise.all([
|
|
1593
|
+
client.getAccount(),
|
|
1594
|
+
client.listAccounts()
|
|
1595
|
+
]);
|
|
2481
1596
|
return {
|
|
2482
|
-
content: [{
|
|
1597
|
+
content: [{
|
|
1598
|
+
type: "text",
|
|
1599
|
+
text: JSON.stringify({ account, connectedAccounts: accountsData }, null, 2)
|
|
1600
|
+
}]
|
|
2483
1601
|
};
|
|
2484
1602
|
}
|
|
2485
1603
|
);
|
|
@@ -2490,7 +1608,7 @@ import { z as z8 } from "zod";
|
|
|
2490
1608
|
function registerBrandVoiceTools(server, client) {
|
|
2491
1609
|
server.tool(
|
|
2492
1610
|
"get_brand_voice",
|
|
2493
|
-
"Get the
|
|
1611
|
+
"Get the brand voice profile and writing rules. Call before creating content.",
|
|
2494
1612
|
{},
|
|
2495
1613
|
{
|
|
2496
1614
|
title: "Get Brand Voice",
|
|
@@ -2565,21 +1683,14 @@ function registerBrandVoiceTools(server, client) {
|
|
|
2565
1683
|
);
|
|
2566
1684
|
server.tool(
|
|
2567
1685
|
"update_brand_voice",
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
-
|
|
2575
|
-
|
|
2576
|
-
{
|
|
2577
|
-
name: z8.string().max(80).optional().describe("Brand voice profile name (e.g. 'BOQtoday Voice', 'Lightbreak Editorial')"),
|
|
2578
|
-
description: z8.string().max(16e3).optional().describe("Overall voice description \u2014 tone, personality, style overview"),
|
|
2579
|
-
dos: z8.array(z8.string()).max(50).optional().describe("Writing rules to follow (array of do's). Send the FULL list, not just additions."),
|
|
2580
|
-
donts: z8.array(z8.string()).max(50).optional().describe("Things to avoid (array of don'ts). Send the FULL list, not just additions."),
|
|
2581
|
-
platform_rules: z8.record(z8.string()).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars, punchy tone", "linkedin": "Professional, add hashtags" }'),
|
|
2582
|
-
example_posts: z8.array(z8.string()).max(8).optional().describe("Example posts that demonstrate the desired voice (max 8)"),
|
|
1686
|
+
"Update the brand voice profile. Set confirmed=false to preview first.",
|
|
1687
|
+
{
|
|
1688
|
+
name: z8.string().max(80).optional().describe("Brand voice profile name"),
|
|
1689
|
+
description: z8.string().max(2e3).optional().describe("Overall voice description"),
|
|
1690
|
+
dos: z8.array(z8.string().max(200)).max(15).optional().describe("Writing rules to follow. Send the FULL list, not just additions."),
|
|
1691
|
+
donts: z8.array(z8.string().max(200)).max(15).optional().describe("Things to avoid. Send the FULL list, not just additions."),
|
|
1692
|
+
platform_rules: z8.record(z8.string().max(300)).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars" }'),
|
|
1693
|
+
example_posts: z8.array(z8.string().max(500)).max(5).optional().describe("Example posts that demonstrate the desired voice"),
|
|
2583
1694
|
confirmed: z8.boolean().default(false).describe(
|
|
2584
1695
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
2585
1696
|
)
|
|
@@ -2597,7 +1708,19 @@ USAGE GUIDELINES:
|
|
|
2597
1708
|
if (args.description !== void 0) payload.description = args.description;
|
|
2598
1709
|
if (args.dos !== void 0) payload.dos = args.dos;
|
|
2599
1710
|
if (args.donts !== void 0) payload.donts = args.donts;
|
|
2600
|
-
if (args.platform_rules !== void 0)
|
|
1711
|
+
if (args.platform_rules !== void 0) {
|
|
1712
|
+
const entries = Object.entries(args.platform_rules);
|
|
1713
|
+
if (entries.length > 5) {
|
|
1714
|
+
return {
|
|
1715
|
+
content: [{
|
|
1716
|
+
type: "text",
|
|
1717
|
+
text: "Maximum 5 platform rules allowed."
|
|
1718
|
+
}],
|
|
1719
|
+
isError: true
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
payload.platformRules = args.platform_rules;
|
|
1723
|
+
}
|
|
2601
1724
|
if (args.example_posts !== void 0) payload.examplePosts = args.example_posts;
|
|
2602
1725
|
if (Object.keys(payload).length === 0) {
|
|
2603
1726
|
return {
|
|
@@ -2685,7 +1808,7 @@ import { z as z9 } from "zod";
|
|
|
2685
1808
|
function registerKnowledgeTools(server, client) {
|
|
2686
1809
|
server.tool(
|
|
2687
1810
|
"get_knowledge_base",
|
|
2688
|
-
"Get all items from the
|
|
1811
|
+
"Get all items from the knowledge base.",
|
|
2689
1812
|
{},
|
|
2690
1813
|
{
|
|
2691
1814
|
title: "Get Knowledge Base",
|
|
@@ -2739,7 +1862,7 @@ function registerKnowledgeTools(server, client) {
|
|
|
2739
1862
|
);
|
|
2740
1863
|
server.tool(
|
|
2741
1864
|
"search_knowledge",
|
|
2742
|
-
"Search the
|
|
1865
|
+
"Search the knowledge base by tag or keyword.",
|
|
2743
1866
|
{
|
|
2744
1867
|
query: z9.string().describe(
|
|
2745
1868
|
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
@@ -2790,7 +1913,7 @@ function registerKnowledgeTools(server, client) {
|
|
|
2790
1913
|
);
|
|
2791
1914
|
server.tool(
|
|
2792
1915
|
"add_knowledge",
|
|
2793
|
-
"Add reference material to the
|
|
1916
|
+
"Add reference material to the knowledge base. Include tags for categorization.",
|
|
2794
1917
|
{
|
|
2795
1918
|
title: z9.string().describe("Title for the knowledge item"),
|
|
2796
1919
|
content: z9.string().describe("The content/text to save"),
|
|
@@ -2832,10 +1955,8 @@ import { z as z10 } from "zod";
|
|
|
2832
1955
|
function registerAudienceTools(server, client) {
|
|
2833
1956
|
server.tool(
|
|
2834
1957
|
"get_audience",
|
|
2835
|
-
"Get the target audience profile
|
|
2836
|
-
{
|
|
2837
|
-
audienceId: z10.string().optional().describe("Specific audience profile ID. If omitted, returns the default audience.")
|
|
2838
|
-
},
|
|
1958
|
+
"Get the target audience profile. Call before creating content.",
|
|
1959
|
+
{},
|
|
2839
1960
|
{
|
|
2840
1961
|
title: "Get Audience Profile",
|
|
2841
1962
|
readOnlyHint: true,
|
|
@@ -2843,9 +1964,9 @@ function registerAudienceTools(server, client) {
|
|
|
2843
1964
|
idempotentHint: true,
|
|
2844
1965
|
openWorldHint: false
|
|
2845
1966
|
},
|
|
2846
|
-
async (
|
|
1967
|
+
async () => {
|
|
2847
1968
|
try {
|
|
2848
|
-
const audience =
|
|
1969
|
+
const audience = await client.getDefaultAudience();
|
|
2849
1970
|
const lines = [];
|
|
2850
1971
|
lines.push(`## Target Audience: ${audience.name}`);
|
|
2851
1972
|
lines.push("");
|
|
@@ -2909,25 +2030,16 @@ function registerAudienceTools(server, client) {
|
|
|
2909
2030
|
);
|
|
2910
2031
|
server.tool(
|
|
2911
2032
|
"update_audience",
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
USAGE GUIDELINES:
|
|
2915
|
-
- Call get_audience first to see the current state before making changes
|
|
2916
|
-
- Always set confirmed=false first to preview the changes, then confirmed=true after user approval
|
|
2917
|
-
- When updating array fields (pain_points, motivations, preferred_platforms), include the FULL array (existing + new items), not just the new ones
|
|
2918
|
-
- Omit audience_id to update the default audience or create a new one if none exists
|
|
2919
|
-
- Max limits: name 100 chars, description 500 chars`,
|
|
2033
|
+
"Update the audience profile. Set confirmed=false to preview first.",
|
|
2920
2034
|
{
|
|
2921
|
-
|
|
2922
|
-
name: z10.string().max(100).optional().describe("Audience profile name (e.g. 'SaaS Founders', 'Health-Conscious Millennials'). Required when creating a new audience."),
|
|
2035
|
+
name: z10.string().max(100).optional().describe("Audience profile name. Required when creating a new audience."),
|
|
2923
2036
|
description: z10.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
2924
|
-
demographics: z10.string().optional().describe("Demographic details
|
|
2925
|
-
pain_points: z10.array(z10.string()).optional().describe("Problems
|
|
2926
|
-
motivations: z10.array(z10.string()).optional().describe("Goals
|
|
2927
|
-
preferred_platforms: z10.array(z10.string()).optional().describe("Social platforms the audience is most active on
|
|
2928
|
-
tone_notes: z10.string().optional().describe("How to speak to this audience
|
|
2929
|
-
content_preferences: z10.string().optional().describe("What content formats and topics resonate
|
|
2930
|
-
is_default: z10.boolean().optional().describe("Set to true to make this the default audience profile"),
|
|
2037
|
+
demographics: z10.string().max(500).optional().describe("Demographic details"),
|
|
2038
|
+
pain_points: z10.array(z10.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
2039
|
+
motivations: z10.array(z10.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
2040
|
+
preferred_platforms: z10.array(z10.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
2041
|
+
tone_notes: z10.string().max(500).optional().describe("How to speak to this audience"),
|
|
2042
|
+
content_preferences: z10.string().max(500).optional().describe("What content formats and topics resonate"),
|
|
2931
2043
|
confirmed: z10.boolean().default(false).describe(
|
|
2932
2044
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
2933
2045
|
)
|
|
@@ -2949,27 +2061,24 @@ USAGE GUIDELINES:
|
|
|
2949
2061
|
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
2950
2062
|
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
2951
2063
|
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2952
|
-
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
2953
2064
|
if (Object.keys(payload).length === 0) {
|
|
2954
2065
|
return {
|
|
2955
2066
|
content: [
|
|
2956
2067
|
{
|
|
2957
2068
|
type: "text",
|
|
2958
|
-
text: "No fields provided. Specify at least one field to update
|
|
2069
|
+
text: "No fields provided. Specify at least one field to update."
|
|
2959
2070
|
}
|
|
2960
2071
|
],
|
|
2961
2072
|
isError: true
|
|
2962
2073
|
};
|
|
2963
2074
|
}
|
|
2964
|
-
let targetId
|
|
2075
|
+
let targetId;
|
|
2965
2076
|
let isCreating = false;
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
isCreating = true;
|
|
2972
|
-
}
|
|
2077
|
+
try {
|
|
2078
|
+
const defaultAudience = await client.getDefaultAudience();
|
|
2079
|
+
targetId = String(defaultAudience.id);
|
|
2080
|
+
} catch {
|
|
2081
|
+
isCreating = true;
|
|
2973
2082
|
}
|
|
2974
2083
|
if (isCreating && !payload.name) {
|
|
2975
2084
|
return {
|
|
@@ -2985,9 +2094,6 @@ USAGE GUIDELINES:
|
|
|
2985
2094
|
if (args.confirmed !== true) {
|
|
2986
2095
|
const lines2 = [];
|
|
2987
2096
|
lines2.push(isCreating ? "## New Audience Preview" : "## Audience Update Preview");
|
|
2988
|
-
if (!isCreating) {
|
|
2989
|
-
lines2.push(`**Audience ID:** ${targetId}`);
|
|
2990
|
-
}
|
|
2991
2097
|
lines2.push("");
|
|
2992
2098
|
if (payload.name) {
|
|
2993
2099
|
lines2.push(`**Name:** ${payload.name}`);
|
|
@@ -3035,10 +2141,6 @@ USAGE GUIDELINES:
|
|
|
3035
2141
|
lines2.push(String(payload.contentPreferences));
|
|
3036
2142
|
lines2.push("");
|
|
3037
2143
|
}
|
|
3038
|
-
if (payload.isDefault !== void 0) {
|
|
3039
|
-
lines2.push(`**Set as default:** ${payload.isDefault ? "Yes" : "No"}`);
|
|
3040
|
-
lines2.push("");
|
|
3041
|
-
}
|
|
3042
2144
|
lines2.push("---");
|
|
3043
2145
|
lines2.push("Call this tool again with **confirmed=true** to save these changes.");
|
|
3044
2146
|
return {
|
|
@@ -3064,7 +2166,6 @@ USAGE GUIDELINES:
|
|
|
3064
2166
|
if (payload.platforms) summary.push(`${payload.platforms.length} platforms`);
|
|
3065
2167
|
if (payload.toneNotes) summary.push("tone notes");
|
|
3066
2168
|
if (payload.contentPreferences) summary.push("content preferences");
|
|
3067
|
-
if (payload.isDefault !== void 0) summary.push("default status");
|
|
3068
2169
|
lines.push(`**${isCreating ? "Created with" : "Updated"}:** ${summary.join(", ")}`);
|
|
3069
2170
|
return {
|
|
3070
2171
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
@@ -3108,7 +2209,7 @@ Follow these rules when drafting:
|
|
|
3108
2209
|
function registerNewsletterTemplateTools(server, client) {
|
|
3109
2210
|
server.tool(
|
|
3110
2211
|
"get_newsletter_template",
|
|
3111
|
-
"Get
|
|
2212
|
+
"Get a newsletter template's structure, sections, and HTML. Pass templateId or omit for default.",
|
|
3112
2213
|
{
|
|
3113
2214
|
templateId: z11.string().optional().describe(
|
|
3114
2215
|
"Specific template ID. If omitted, returns the default template."
|
|
@@ -3209,6 +2310,15 @@ function registerNewsletterTemplateTools(server, client) {
|
|
|
3209
2310
|
);
|
|
3210
2311
|
lines.push("");
|
|
3211
2312
|
}
|
|
2313
|
+
if (template.htmlContent) {
|
|
2314
|
+
lines.push("### HTML Template:");
|
|
2315
|
+
lines.push("The following HTML skeleton contains {{placeholder}} variables. Fill in the placeholders with real content when generating the newsletter.");
|
|
2316
|
+
lines.push("");
|
|
2317
|
+
lines.push("```html");
|
|
2318
|
+
lines.push(template.htmlContent);
|
|
2319
|
+
lines.push("```");
|
|
2320
|
+
lines.push("");
|
|
2321
|
+
}
|
|
3212
2322
|
if (template.rssFeedUrls && template.rssFeedUrls.length > 0) {
|
|
3213
2323
|
lines.push("### Linked RSS Feeds:");
|
|
3214
2324
|
for (const url of template.rssFeedUrls) {
|
|
@@ -3245,9 +2355,10 @@ function registerNewsletterTemplateTools(server, client) {
|
|
|
3245
2355
|
);
|
|
3246
2356
|
server.tool(
|
|
3247
2357
|
"get_past_newsletters",
|
|
3248
|
-
"
|
|
2358
|
+
"Get past newsletters from the archive. Pass newsletterId to get full content of a specific edition.",
|
|
3249
2359
|
{
|
|
3250
|
-
|
|
2360
|
+
newsletterId: z11.string().optional().describe("If provided, returns the full content of this specific newsletter. If omitted, returns the list."),
|
|
2361
|
+
limit: z11.number().optional().describe("Number of past newsletters to retrieve (when listing). Default 5, max 50."),
|
|
3251
2362
|
templateId: z11.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
3252
2363
|
},
|
|
3253
2364
|
{
|
|
@@ -3257,7 +2368,41 @@ function registerNewsletterTemplateTools(server, client) {
|
|
|
3257
2368
|
idempotentHint: true,
|
|
3258
2369
|
openWorldHint: false
|
|
3259
2370
|
},
|
|
3260
|
-
async ({ limit, templateId }) => {
|
|
2371
|
+
async ({ newsletterId, limit, templateId }) => {
|
|
2372
|
+
if (newsletterId) {
|
|
2373
|
+
try {
|
|
2374
|
+
const newsletter = await client.getArchivedNewsletter(
|
|
2375
|
+
newsletterId
|
|
2376
|
+
);
|
|
2377
|
+
const lines = [];
|
|
2378
|
+
lines.push(`## ${newsletter.subject}`);
|
|
2379
|
+
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
2380
|
+
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
2381
|
+
lines.push("");
|
|
2382
|
+
lines.push("### Full HTML Content:");
|
|
2383
|
+
lines.push(newsletter.contentHtml);
|
|
2384
|
+
return {
|
|
2385
|
+
content: [
|
|
2386
|
+
{ type: "text", text: lines.join("\n") },
|
|
2387
|
+
...newsletter.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2388
|
+
${newsletter.contentHtml}` }] : []
|
|
2389
|
+
]
|
|
2390
|
+
};
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2393
|
+
if (message.includes("404")) {
|
|
2394
|
+
return {
|
|
2395
|
+
content: [
|
|
2396
|
+
{
|
|
2397
|
+
type: "text",
|
|
2398
|
+
text: "Newsletter not found. Check the ID and try again."
|
|
2399
|
+
}
|
|
2400
|
+
]
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
throw error;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
3261
2406
|
try {
|
|
3262
2407
|
const params = {};
|
|
3263
2408
|
if (limit) params.limit = String(Math.min(limit, 50));
|
|
@@ -3295,101 +2440,266 @@ function registerNewsletterTemplateTools(server, client) {
|
|
|
3295
2440
|
const textContent = nl.contentHtml ? nl.contentHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 500) : "(no content)";
|
|
3296
2441
|
lines.push(textContent);
|
|
3297
2442
|
lines.push(
|
|
3298
|
-
`[Full content available
|
|
2443
|
+
`[Full content available by calling get_past_newsletters with newsletterId: ${nl.id}]`
|
|
2444
|
+
);
|
|
2445
|
+
lines.push("");
|
|
2446
|
+
}
|
|
2447
|
+
const mostRecent = newsletters[0];
|
|
2448
|
+
return {
|
|
2449
|
+
content: [
|
|
2450
|
+
{ type: "text", text: lines.join("\n") },
|
|
2451
|
+
...mostRecent?.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2452
|
+
${mostRecent.contentHtml}` }] : []
|
|
2453
|
+
]
|
|
2454
|
+
};
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2457
|
+
throw new Error(`Failed to fetch past newsletters: ${message}`);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
);
|
|
2461
|
+
server.tool(
|
|
2462
|
+
"save_newsletter",
|
|
2463
|
+
"Save an approved newsletter to the archive for future reference.",
|
|
2464
|
+
{
|
|
2465
|
+
subject: z11.string().describe("The newsletter subject line."),
|
|
2466
|
+
contentHtml: z11.string().describe("The full HTML content of the newsletter."),
|
|
2467
|
+
templateId: z11.string().optional().describe("The template ID used to generate this newsletter."),
|
|
2468
|
+
notes: z11.string().optional().describe(
|
|
2469
|
+
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2470
|
+
)
|
|
2471
|
+
},
|
|
2472
|
+
{
|
|
2473
|
+
title: "Save Newsletter to Archive",
|
|
2474
|
+
readOnlyHint: false,
|
|
2475
|
+
destructiveHint: false,
|
|
2476
|
+
idempotentHint: false,
|
|
2477
|
+
openWorldHint: false
|
|
2478
|
+
},
|
|
2479
|
+
async ({ subject, contentHtml, templateId, notes }) => {
|
|
2480
|
+
try {
|
|
2481
|
+
const data = {
|
|
2482
|
+
subject,
|
|
2483
|
+
contentHtml
|
|
2484
|
+
};
|
|
2485
|
+
if (templateId) data.templateId = Number(templateId);
|
|
2486
|
+
if (notes) data.notes = notes;
|
|
2487
|
+
await client.saveNewsletterToArchive(data);
|
|
2488
|
+
return {
|
|
2489
|
+
content: [
|
|
2490
|
+
{
|
|
2491
|
+
type: "text",
|
|
2492
|
+
text: `Newsletter "${subject}" has been saved to the archive successfully.`
|
|
2493
|
+
},
|
|
2494
|
+
...contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2495
|
+
${contentHtml}` }] : []
|
|
2496
|
+
]
|
|
2497
|
+
};
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2500
|
+
throw new Error(`Failed to save newsletter: ${message}`);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
);
|
|
2504
|
+
server.tool(
|
|
2505
|
+
"save_newsletter_template",
|
|
2506
|
+
"Create or update a newsletter template with HTML and section definitions.",
|
|
2507
|
+
{
|
|
2508
|
+
template_id: z11.string().optional().describe(
|
|
2509
|
+
"If provided, updates this existing template. Otherwise creates a new one."
|
|
2510
|
+
),
|
|
2511
|
+
name: z11.string().min(1).max(255).describe("Template name"),
|
|
2512
|
+
description: z11.string().max(2e3).optional().describe("What this template is for"),
|
|
2513
|
+
html_content: z11.string().min(1).max(1e5).describe(
|
|
2514
|
+
"Full HTML template with {{placeholder}} variables."
|
|
2515
|
+
),
|
|
2516
|
+
sections: z11.array(
|
|
2517
|
+
z11.object({
|
|
2518
|
+
name: z11.string().describe("Section identifier, e.g. 'intro'"),
|
|
2519
|
+
label: z11.string().describe("Display label, e.g. 'Intro/Greeting'"),
|
|
2520
|
+
required: z11.boolean().optional().describe("Whether this section is required"),
|
|
2521
|
+
item_count: z11.number().optional().describe("Number of items in this section, if applicable")
|
|
2522
|
+
})
|
|
2523
|
+
).max(20).optional().describe("Array of section metadata describing the template structure"),
|
|
2524
|
+
style_config: z11.object({
|
|
2525
|
+
primary_color: z11.string().optional(),
|
|
2526
|
+
accent_color: z11.string().optional(),
|
|
2527
|
+
link_color: z11.string().optional(),
|
|
2528
|
+
background_color: z11.string().optional(),
|
|
2529
|
+
font_family: z11.string().optional(),
|
|
2530
|
+
max_width: z11.string().optional()
|
|
2531
|
+
}).optional().describe("Colors, fonts, and brand configuration"),
|
|
2532
|
+
is_default: z11.boolean().optional().describe(
|
|
2533
|
+
"Set as the default template for this customer."
|
|
2534
|
+
)
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
title: "Save Newsletter Template",
|
|
2538
|
+
readOnlyHint: false,
|
|
2539
|
+
destructiveHint: false,
|
|
2540
|
+
idempotentHint: false,
|
|
2541
|
+
openWorldHint: false
|
|
2542
|
+
},
|
|
2543
|
+
async ({
|
|
2544
|
+
template_id,
|
|
2545
|
+
name,
|
|
2546
|
+
description,
|
|
2547
|
+
html_content,
|
|
2548
|
+
sections,
|
|
2549
|
+
style_config,
|
|
2550
|
+
is_default
|
|
2551
|
+
}) => {
|
|
2552
|
+
try {
|
|
2553
|
+
const data = {
|
|
2554
|
+
name,
|
|
2555
|
+
htmlContent: html_content
|
|
2556
|
+
};
|
|
2557
|
+
if (description !== void 0) data.description = description;
|
|
2558
|
+
if (sections !== void 0) {
|
|
2559
|
+
data.sections = sections.map((s) => ({
|
|
2560
|
+
id: s.name,
|
|
2561
|
+
type: "custom",
|
|
2562
|
+
label: s.label,
|
|
2563
|
+
required: s.required ?? true,
|
|
2564
|
+
count: s.item_count
|
|
2565
|
+
}));
|
|
2566
|
+
}
|
|
2567
|
+
if (style_config !== void 0) {
|
|
2568
|
+
data.style = {
|
|
2569
|
+
primary_color: style_config.primary_color,
|
|
2570
|
+
accent_color: style_config.accent_color,
|
|
2571
|
+
link_color: style_config.link_color,
|
|
2572
|
+
background_color: style_config.background_color,
|
|
2573
|
+
font_family: style_config.font_family,
|
|
2574
|
+
content_width: style_config.max_width
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
if (is_default !== void 0) data.isDefault = is_default;
|
|
2578
|
+
let result;
|
|
2579
|
+
if (template_id) {
|
|
2580
|
+
result = await client.updateTemplate(
|
|
2581
|
+
template_id,
|
|
2582
|
+
data
|
|
3299
2583
|
);
|
|
3300
|
-
|
|
2584
|
+
} else {
|
|
2585
|
+
result = await client.createTemplate(data);
|
|
3301
2586
|
}
|
|
2587
|
+
const action = template_id ? "updated" : "created";
|
|
3302
2588
|
return {
|
|
3303
|
-
content: [
|
|
2589
|
+
content: [
|
|
2590
|
+
{
|
|
2591
|
+
type: "text",
|
|
2592
|
+
text: `Newsletter template "${result.name}" ${action} successfully (ID: ${result.id}).${result.isDefault ? " Set as default." : ""}`
|
|
2593
|
+
}
|
|
2594
|
+
]
|
|
3304
2595
|
};
|
|
3305
2596
|
} catch (error) {
|
|
3306
2597
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3307
|
-
throw new Error(`Failed to
|
|
2598
|
+
throw new Error(`Failed to save newsletter template: ${message}`);
|
|
3308
2599
|
}
|
|
3309
2600
|
}
|
|
3310
2601
|
);
|
|
3311
2602
|
server.tool(
|
|
3312
|
-
"
|
|
3313
|
-
"
|
|
3314
|
-
{
|
|
3315
|
-
newsletterId: z11.string().describe("The ID of the archived newsletter to retrieve.")
|
|
3316
|
-
},
|
|
2603
|
+
"list_newsletter_templates",
|
|
2604
|
+
"List all saved newsletter templates.",
|
|
2605
|
+
{},
|
|
3317
2606
|
{
|
|
3318
|
-
title: "
|
|
2607
|
+
title: "List Newsletter Templates",
|
|
3319
2608
|
readOnlyHint: true,
|
|
3320
2609
|
destructiveHint: false,
|
|
3321
2610
|
idempotentHint: true,
|
|
3322
2611
|
openWorldHint: false
|
|
3323
2612
|
},
|
|
3324
|
-
async (
|
|
2613
|
+
async () => {
|
|
3325
2614
|
try {
|
|
3326
|
-
const
|
|
3327
|
-
|
|
3328
|
-
);
|
|
3329
|
-
const lines = [];
|
|
3330
|
-
lines.push(`## ${newsletter.subject}`);
|
|
3331
|
-
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
3332
|
-
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
3333
|
-
lines.push("");
|
|
3334
|
-
lines.push("### Full HTML Content:");
|
|
3335
|
-
lines.push(newsletter.contentHtml);
|
|
3336
|
-
return {
|
|
3337
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
3338
|
-
};
|
|
3339
|
-
} catch (error) {
|
|
3340
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3341
|
-
if (message.includes("404")) {
|
|
2615
|
+
const templates = await client.listTemplates();
|
|
2616
|
+
if (!templates || templates.length === 0) {
|
|
3342
2617
|
return {
|
|
3343
2618
|
content: [
|
|
3344
2619
|
{
|
|
3345
2620
|
type: "text",
|
|
3346
|
-
text: "
|
|
2621
|
+
text: "No newsletter templates found. Use save_newsletter_template to create one."
|
|
3347
2622
|
}
|
|
3348
2623
|
]
|
|
3349
2624
|
};
|
|
3350
2625
|
}
|
|
3351
|
-
|
|
2626
|
+
const lines = [];
|
|
2627
|
+
lines.push(`## Newsletter Templates (${templates.length})`);
|
|
2628
|
+
lines.push("");
|
|
2629
|
+
for (const t of templates) {
|
|
2630
|
+
const badges = [];
|
|
2631
|
+
if (t.isDefault) badges.push("DEFAULT");
|
|
2632
|
+
if (t.htmlContent) badges.push("HAS HTML");
|
|
2633
|
+
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
2634
|
+
lines.push(`**${t.name}** (ID: ${t.id})${badgeStr}`);
|
|
2635
|
+
if (t.description) lines.push(` ${t.description}`);
|
|
2636
|
+
if (t.sections && t.sections.length > 0) {
|
|
2637
|
+
lines.push(
|
|
2638
|
+
` Sections: ${t.sections.map((s) => s.label).join(", ")}`
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
lines.push("");
|
|
2642
|
+
}
|
|
2643
|
+
return {
|
|
2644
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
2645
|
+
};
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2648
|
+
throw new Error(`Failed to list newsletter templates: ${message}`);
|
|
3352
2649
|
}
|
|
3353
2650
|
}
|
|
3354
2651
|
);
|
|
3355
2652
|
server.tool(
|
|
3356
|
-
"
|
|
3357
|
-
"
|
|
2653
|
+
"delete_newsletter_template",
|
|
2654
|
+
"Delete a newsletter template. Set confirmed=true to proceed.",
|
|
3358
2655
|
{
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
notes: z11.string().optional().describe(
|
|
3363
|
-
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2656
|
+
template_id: z11.string().describe("The ID of the newsletter template to delete."),
|
|
2657
|
+
confirmed: z11.boolean().default(false).describe(
|
|
2658
|
+
"Must be true to actually delete. If false, returns a confirmation prompt."
|
|
3364
2659
|
)
|
|
3365
2660
|
},
|
|
3366
2661
|
{
|
|
3367
|
-
title: "
|
|
2662
|
+
title: "Delete Newsletter Template",
|
|
3368
2663
|
readOnlyHint: false,
|
|
3369
|
-
destructiveHint:
|
|
2664
|
+
destructiveHint: true,
|
|
3370
2665
|
idempotentHint: false,
|
|
3371
2666
|
openWorldHint: false
|
|
3372
2667
|
},
|
|
3373
|
-
async ({
|
|
2668
|
+
async ({ template_id, confirmed }) => {
|
|
3374
2669
|
try {
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
2670
|
+
if (!confirmed) {
|
|
2671
|
+
const template = await client.getTemplate(template_id);
|
|
2672
|
+
return {
|
|
2673
|
+
content: [
|
|
2674
|
+
{
|
|
2675
|
+
type: "text",
|
|
2676
|
+
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.`
|
|
2677
|
+
}
|
|
2678
|
+
]
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
await client.deleteTemplate(template_id);
|
|
3382
2682
|
return {
|
|
3383
2683
|
content: [
|
|
3384
2684
|
{
|
|
3385
2685
|
type: "text",
|
|
3386
|
-
text: `Newsletter
|
|
2686
|
+
text: `Newsletter template (ID: ${template_id}) has been deleted.`
|
|
3387
2687
|
}
|
|
3388
2688
|
]
|
|
3389
2689
|
};
|
|
3390
2690
|
} catch (error) {
|
|
3391
2691
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3392
|
-
|
|
2692
|
+
if (message.includes("404")) {
|
|
2693
|
+
return {
|
|
2694
|
+
content: [
|
|
2695
|
+
{
|
|
2696
|
+
type: "text",
|
|
2697
|
+
text: "Template not found. It may have already been deleted."
|
|
2698
|
+
}
|
|
2699
|
+
]
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
throw new Error(`Failed to delete newsletter template: ${message}`);
|
|
3393
2703
|
}
|
|
3394
2704
|
}
|
|
3395
2705
|
);
|
|
@@ -3400,7 +2710,7 @@ import { z as z12 } from "zod";
|
|
|
3400
2710
|
function registerCalendarTools(server, client) {
|
|
3401
2711
|
server.tool(
|
|
3402
2712
|
"get_calendar",
|
|
3403
|
-
"View the
|
|
2713
|
+
"View the content calendar with all scheduled, published, and draft posts.",
|
|
3404
2714
|
{
|
|
3405
2715
|
from: z12.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
3406
2716
|
to: z12.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
@@ -3483,7 +2793,7 @@ function registerCalendarTools(server, client) {
|
|
|
3483
2793
|
);
|
|
3484
2794
|
server.tool(
|
|
3485
2795
|
"get_queue",
|
|
3486
|
-
"View the
|
|
2796
|
+
"View the posting queue schedule and recurring weekly time slots.",
|
|
3487
2797
|
{},
|
|
3488
2798
|
{
|
|
3489
2799
|
title: "Get Posting Queue",
|
|
@@ -3517,7 +2827,8 @@ function registerCalendarTools(server, client) {
|
|
|
3517
2827
|
for (const slot of slots) {
|
|
3518
2828
|
const day = dayNames[slot.dayOfWeek] ?? `Day ${slot.dayOfWeek}`;
|
|
3519
2829
|
const platforms = (slot.platforms ?? []).map((p) => `[${p}]`).join(" ");
|
|
3520
|
-
|
|
2830
|
+
const slotId = slot.id ?? slot._id ?? `${slot.dayOfWeek}_${slot.time}`;
|
|
2831
|
+
text += `${day}: ${slot.time} ${platforms} (id: ${slotId})
|
|
3521
2832
|
`;
|
|
3522
2833
|
}
|
|
3523
2834
|
}
|
|
@@ -3538,17 +2849,7 @@ Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
|
3538
2849
|
);
|
|
3539
2850
|
server.tool(
|
|
3540
2851
|
"schedule_to_queue",
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
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.
|
|
3544
|
-
|
|
3545
|
-
REQUIRED WORKFLOW:
|
|
3546
|
-
1. BEFORE creating content, call get_brand_voice and get_audience
|
|
3547
|
-
2. ALWAYS set confirmed=false first to preview
|
|
3548
|
-
3. Show the user a visual preview before confirming
|
|
3549
|
-
4. Tell the user which queue slot the post will be assigned to
|
|
3550
|
-
|
|
3551
|
-
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.`,
|
|
2852
|
+
"Add a post to the next available queue slot. Check get_queue first. Set confirmed=false to preview first.",
|
|
3552
2853
|
{
|
|
3553
2854
|
content: z12.string().describe("The text content of the post"),
|
|
3554
2855
|
platforms: z12.array(z12.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -3649,7 +2950,7 @@ function timeAgo(dateStr) {
|
|
|
3649
2950
|
function registerNotificationTools(server, client) {
|
|
3650
2951
|
server.tool(
|
|
3651
2952
|
"get_notifications",
|
|
3652
|
-
"Get recent notifications including post
|
|
2953
|
+
"Get recent notifications including post results, account warnings, and errors.",
|
|
3653
2954
|
{
|
|
3654
2955
|
unread_only: z13.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
3655
2956
|
limit: z13.number().optional().describe("Max notifications to return. Default 10.")
|
|
@@ -3694,7 +2995,7 @@ No notifications found. All clear!`
|
|
|
3694
2995
|
const action = n.actionLabel || "View";
|
|
3695
2996
|
if (n.resourceType === "post" && n.resourceId) {
|
|
3696
2997
|
line += `
|
|
3697
|
-
Action: ${action} \u2192
|
|
2998
|
+
Action: ${action} \u2192 get_post with post_id "${n.resourceId}"`;
|
|
3698
2999
|
} else {
|
|
3699
3000
|
line += `
|
|
3700
3001
|
Action: ${action} \u2192 ${n.actionUrl}`;
|
|
@@ -3719,10 +3020,36 @@ Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" :
|
|
|
3719
3020
|
}
|
|
3720
3021
|
|
|
3721
3022
|
// src/tools/publishing-rules.ts
|
|
3023
|
+
var WORKFLOW = {
|
|
3024
|
+
before_writing_content: [
|
|
3025
|
+
"Call get_brand_voice to load tone and style rules",
|
|
3026
|
+
"Call get_audience to understand who the content is for",
|
|
3027
|
+
"For newsletters: also call get_newsletter_template and get_past_newsletters"
|
|
3028
|
+
],
|
|
3029
|
+
before_publishing: [
|
|
3030
|
+
"Always set confirmed=false first to generate a preview",
|
|
3031
|
+
"Show the user a visual preview before confirming",
|
|
3032
|
+
"Never set confirmed=true without explicit user approval",
|
|
3033
|
+
"For newsletters: show subscriber count and send time before confirming",
|
|
3034
|
+
"If require_double_confirm is true, ask for confirmation twice"
|
|
3035
|
+
],
|
|
3036
|
+
replies_and_engagement: [
|
|
3037
|
+
"Draft the reply and show it before sending",
|
|
3038
|
+
"Set confirmed=false first to preview",
|
|
3039
|
+
"Remind user that replies are public"
|
|
3040
|
+
],
|
|
3041
|
+
newsletters: [
|
|
3042
|
+
"Always render newsletter as HTML artifact/preview before pushing to ESP",
|
|
3043
|
+
"Use table-based layouts, inline CSS only, 600px max width",
|
|
3044
|
+
"Email-safe fonts only: Arial, Helvetica, Georgia, Verdana",
|
|
3045
|
+
"All images must use absolute URLs",
|
|
3046
|
+
"Keep total email under 102KB to avoid Gmail clipping"
|
|
3047
|
+
]
|
|
3048
|
+
};
|
|
3722
3049
|
function registerPublishingRulesTools(server, client) {
|
|
3723
3050
|
server.tool(
|
|
3724
3051
|
"get_publishing_rules",
|
|
3725
|
-
|
|
3052
|
+
"Get publishing rules and the content creation workflow. Call this BEFORE publishing, scheduling, or sending any content.",
|
|
3726
3053
|
{},
|
|
3727
3054
|
{
|
|
3728
3055
|
title: "Get Publishing Rules",
|
|
@@ -3733,90 +3060,30 @@ function registerPublishingRulesTools(server, client) {
|
|
|
3733
3060
|
},
|
|
3734
3061
|
async () => {
|
|
3735
3062
|
const rules = await client.getPublishingRules();
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
`;
|
|
3755
|
-
if (rules.blockedWords && rules.blockedWords.length > 0) {
|
|
3756
|
-
text += `Blocked words: **${rules.blockedWords.join(", ")}**
|
|
3757
|
-
`;
|
|
3758
|
-
} else {
|
|
3759
|
-
text += "Blocked words: none\n";
|
|
3760
|
-
}
|
|
3761
|
-
if (rules.requiredDisclaimer) {
|
|
3762
|
-
text += `Required disclaimer: "${rules.requiredDisclaimer}"
|
|
3763
|
-
`;
|
|
3764
|
-
} else {
|
|
3765
|
-
text += "Required disclaimer: none\n";
|
|
3766
|
-
}
|
|
3767
|
-
return { content: [{ type: "text", text }] };
|
|
3768
|
-
}
|
|
3769
|
-
);
|
|
3770
|
-
}
|
|
3771
|
-
|
|
3772
|
-
// src/tools/audit-log.ts
|
|
3773
|
-
import { z as z14 } from "zod";
|
|
3774
|
-
function registerAuditLogTools(server, client) {
|
|
3775
|
-
server.tool(
|
|
3776
|
-
"get_audit_log",
|
|
3777
|
-
"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.",
|
|
3778
|
-
{
|
|
3779
|
-
limit: z14.number().optional().describe("Maximum number of entries to return (default 20, max 200)"),
|
|
3780
|
-
action: z14.string().optional().describe("Filter by action type prefix, e.g. 'post.published', 'newsletter.sent', 'post.' for all post actions"),
|
|
3781
|
-
resource_type: z14.string().optional().describe("Filter by resource type: post, newsletter, account, media, publishing_rules, brand_voice")
|
|
3782
|
-
},
|
|
3783
|
-
{
|
|
3784
|
-
title: "Get Audit Log",
|
|
3785
|
-
readOnlyHint: true,
|
|
3786
|
-
destructiveHint: false,
|
|
3787
|
-
idempotentHint: true,
|
|
3788
|
-
openWorldHint: false
|
|
3789
|
-
},
|
|
3790
|
-
async (args) => {
|
|
3791
|
-
const params = {};
|
|
3792
|
-
if (args.limit) params.limit = String(args.limit);
|
|
3793
|
-
if (args.action) params.action = args.action;
|
|
3794
|
-
if (args.resource_type) params.resource_type = args.resource_type;
|
|
3795
|
-
const data = await client.getAuditLog(params);
|
|
3796
|
-
const entries = data?.entries ?? [];
|
|
3797
|
-
if (entries.length === 0) {
|
|
3798
|
-
return {
|
|
3799
|
-
content: [{ type: "text", text: "## Audit Log\n\nNo entries found." }]
|
|
3800
|
-
};
|
|
3801
|
-
}
|
|
3802
|
-
let text = `## Audit Log (${entries.length} of ${data?.total ?? entries.length} entries)
|
|
3803
|
-
|
|
3804
|
-
`;
|
|
3805
|
-
for (const entry of entries) {
|
|
3806
|
-
const date = entry.createdAt ? new Date(entry.createdAt).toLocaleString() : "unknown";
|
|
3807
|
-
const details = entry.details && Object.keys(entry.details).length > 0 ? ` \u2014 ${JSON.stringify(entry.details)}` : "";
|
|
3808
|
-
text += `- **${date}** | ${entry.action} | ${entry.resourceType}`;
|
|
3809
|
-
if (entry.resourceId) text += ` #${entry.resourceId}`;
|
|
3810
|
-
text += `${details}
|
|
3811
|
-
`;
|
|
3812
|
-
}
|
|
3813
|
-
return { content: [{ type: "text", text }] };
|
|
3063
|
+
const response = {
|
|
3064
|
+
rules: {
|
|
3065
|
+
social_default_action: rules.socialDefaultAction ?? "draft",
|
|
3066
|
+
newsletter_default_action: rules.newsletterDefaultAction ?? "draft",
|
|
3067
|
+
require_preview_before_publish: rules.requirePreviewBeforePublish ?? true,
|
|
3068
|
+
require_double_confirm_newsletter: rules.requireDoubleConfirmNewsletter ?? true,
|
|
3069
|
+
require_double_confirm_social: rules.requireDoubleConfirmSocial ?? false,
|
|
3070
|
+
allow_immediate_publish: rules.allowImmediatePublish ?? false,
|
|
3071
|
+
allow_immediate_send: rules.allowImmediateSend ?? false,
|
|
3072
|
+
max_posts_per_day: rules.maxPostsPerDay ?? null,
|
|
3073
|
+
blocked_words: rules.blockedWords ?? [],
|
|
3074
|
+
required_disclaimer: rules.requiredDisclaimer ?? null
|
|
3075
|
+
},
|
|
3076
|
+
workflow: WORKFLOW
|
|
3077
|
+
};
|
|
3078
|
+
return {
|
|
3079
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
3080
|
+
};
|
|
3814
3081
|
}
|
|
3815
3082
|
);
|
|
3816
3083
|
}
|
|
3817
3084
|
|
|
3818
3085
|
// src/tools/sources.ts
|
|
3819
|
-
import { z as
|
|
3086
|
+
import { z as z14 } from "zod";
|
|
3820
3087
|
var TYPE_LABELS = {
|
|
3821
3088
|
feed: "RSS Feed",
|
|
3822
3089
|
website: "Website",
|
|
@@ -3838,12 +3105,12 @@ var TYPE_HINTS = {
|
|
|
3838
3105
|
function registerSourceTools(server, client) {
|
|
3839
3106
|
server.tool(
|
|
3840
3107
|
"get_sources",
|
|
3841
|
-
"Get
|
|
3108
|
+
"Get saved content sources (RSS feeds, websites, YouTube channels, search topics).",
|
|
3842
3109
|
{
|
|
3843
|
-
type:
|
|
3110
|
+
type: z14.string().optional().describe(
|
|
3844
3111
|
"Optional: filter by type (feed, website, youtube, search, podcast, reddit, social)"
|
|
3845
3112
|
),
|
|
3846
|
-
category:
|
|
3113
|
+
category: z14.string().optional().describe("Optional: filter by category")
|
|
3847
3114
|
},
|
|
3848
3115
|
{
|
|
3849
3116
|
title: "Get Content Sources",
|
|
@@ -3904,13 +3171,13 @@ function registerSourceTools(server, client) {
|
|
|
3904
3171
|
);
|
|
3905
3172
|
server.tool(
|
|
3906
3173
|
"add_source",
|
|
3907
|
-
"Save a new content source to
|
|
3174
|
+
"Save a new content source to monitor.",
|
|
3908
3175
|
{
|
|
3909
|
-
name:
|
|
3910
|
-
url:
|
|
3911
|
-
"URL of the source
|
|
3176
|
+
name: z14.string().describe("Display name for the source"),
|
|
3177
|
+
url: z14.string().optional().describe(
|
|
3178
|
+
"URL of the source. Not needed for 'search' type."
|
|
3912
3179
|
),
|
|
3913
|
-
type:
|
|
3180
|
+
type: z14.enum([
|
|
3914
3181
|
"feed",
|
|
3915
3182
|
"website",
|
|
3916
3183
|
"youtube",
|
|
@@ -3919,12 +3186,12 @@ function registerSourceTools(server, client) {
|
|
|
3919
3186
|
"reddit",
|
|
3920
3187
|
"social"
|
|
3921
3188
|
]).describe("Type of source. Determines how to fetch content from it."),
|
|
3922
|
-
category:
|
|
3923
|
-
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration'
|
|
3189
|
+
category: z14.string().optional().describe(
|
|
3190
|
+
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration')"
|
|
3924
3191
|
),
|
|
3925
|
-
tags:
|
|
3926
|
-
notes:
|
|
3927
|
-
searchQuery:
|
|
3192
|
+
tags: z14.array(z14.string()).optional().describe("Optional tags for filtering"),
|
|
3193
|
+
notes: z14.string().optional().describe("Optional notes about why this source matters"),
|
|
3194
|
+
searchQuery: z14.string().optional().describe(
|
|
3928
3195
|
"For 'search' type only: the keyword or topic to search for"
|
|
3929
3196
|
)
|
|
3930
3197
|
},
|
|
@@ -3964,12 +3231,12 @@ function registerSourceTools(server, client) {
|
|
|
3964
3231
|
);
|
|
3965
3232
|
server.tool(
|
|
3966
3233
|
"update_source",
|
|
3967
|
-
"Update a saved content source.
|
|
3234
|
+
"Update a saved content source.",
|
|
3968
3235
|
{
|
|
3969
|
-
source_id:
|
|
3970
|
-
name:
|
|
3971
|
-
url:
|
|
3972
|
-
type:
|
|
3236
|
+
source_id: z14.number().describe("The ID of the source to update"),
|
|
3237
|
+
name: z14.string().optional().describe("New display name"),
|
|
3238
|
+
url: z14.string().optional().describe("New URL"),
|
|
3239
|
+
type: z14.enum([
|
|
3973
3240
|
"feed",
|
|
3974
3241
|
"website",
|
|
3975
3242
|
"youtube",
|
|
@@ -3978,11 +3245,11 @@ function registerSourceTools(server, client) {
|
|
|
3978
3245
|
"reddit",
|
|
3979
3246
|
"social"
|
|
3980
3247
|
]).optional().describe("New source type"),
|
|
3981
|
-
category:
|
|
3982
|
-
tags:
|
|
3983
|
-
notes:
|
|
3984
|
-
searchQuery:
|
|
3985
|
-
isActive:
|
|
3248
|
+
category: z14.string().optional().describe("New category"),
|
|
3249
|
+
tags: z14.array(z14.string()).optional().describe("New tags"),
|
|
3250
|
+
notes: z14.string().optional().describe("New notes"),
|
|
3251
|
+
searchQuery: z14.string().optional().describe("New search query"),
|
|
3252
|
+
isActive: z14.boolean().optional().describe("Set to false to deactivate")
|
|
3986
3253
|
},
|
|
3987
3254
|
{
|
|
3988
3255
|
title: "Update Content Source",
|
|
@@ -4010,15 +3277,10 @@ function registerSourceTools(server, client) {
|
|
|
4010
3277
|
);
|
|
4011
3278
|
server.tool(
|
|
4012
3279
|
"remove_source",
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
REQUIRED WORKFLOW:
|
|
4016
|
-
1. ALWAYS set confirmed=false first to preview which source will be deleted
|
|
4017
|
-
2. Show the user the source details before confirming
|
|
4018
|
-
3. Only confirm after explicit user approval`,
|
|
3280
|
+
"Remove a content source. Set confirmed=false to preview first.",
|
|
4019
3281
|
{
|
|
4020
|
-
source_id:
|
|
4021
|
-
confirmed:
|
|
3282
|
+
source_id: z14.number().describe("The ID of the source to remove"),
|
|
3283
|
+
confirmed: z14.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
4022
3284
|
},
|
|
4023
3285
|
{
|
|
4024
3286
|
title: "Remove Content Source",
|
|
@@ -4048,237 +3310,14 @@ This will permanently remove this content source. Call this tool again with conf
|
|
|
4048
3310
|
}
|
|
4049
3311
|
);
|
|
4050
3312
|
}
|
|
4051
|
-
|
|
4052
|
-
// src/tools/carousel.ts
|
|
4053
|
-
import { z as z16 } from "zod";
|
|
4054
|
-
function registerCarouselTools(server, client) {
|
|
4055
|
-
server.tool(
|
|
4056
|
-
"get_carousel_template",
|
|
4057
|
-
"Get the customer's carousel template configuration. Returns brand colors, logo, CTA text, and other visual settings used to generate carousels from article URLs.",
|
|
4058
|
-
{},
|
|
4059
|
-
{
|
|
4060
|
-
title: "Get Carousel Template",
|
|
4061
|
-
readOnlyHint: true,
|
|
4062
|
-
destructiveHint: false,
|
|
4063
|
-
idempotentHint: true,
|
|
4064
|
-
openWorldHint: false
|
|
4065
|
-
},
|
|
4066
|
-
async () => {
|
|
4067
|
-
try {
|
|
4068
|
-
const template = await client.getCarouselTemplate();
|
|
4069
|
-
const lines = [];
|
|
4070
|
-
lines.push(`## Carousel Template: ${template.name || "Not configured"}`);
|
|
4071
|
-
lines.push("");
|
|
4072
|
-
if (!template.id) {
|
|
4073
|
-
lines.push("No carousel template has been configured yet.");
|
|
4074
|
-
lines.push("");
|
|
4075
|
-
lines.push("Use `save_carousel_template` to set one up with your brand name, colors, and CTA text.");
|
|
4076
|
-
return {
|
|
4077
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4078
|
-
};
|
|
4079
|
-
}
|
|
4080
|
-
lines.push(`**Brand Name:** ${template.brandName || "\u2014"}`);
|
|
4081
|
-
if (template.logoUrl) lines.push(`**Logo:** ${template.logoUrl}`);
|
|
4082
|
-
lines.push(`**Primary Color:** ${template.colorPrimary || "#1a1a2e"}`);
|
|
4083
|
-
lines.push(`**Secondary Color:** ${template.colorSecondary || "#e94560"}`);
|
|
4084
|
-
lines.push(`**Accent Color:** ${template.colorAccent || "#ffffff"}`);
|
|
4085
|
-
lines.push(`**CTA Text:** ${template.ctaText || "Read the full story \u2192"}`);
|
|
4086
|
-
lines.push(`**Logo Placement:** ${template.logoPlacement || "top-left"}`);
|
|
4087
|
-
lines.push("");
|
|
4088
|
-
return {
|
|
4089
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4090
|
-
};
|
|
4091
|
-
} catch (error) {
|
|
4092
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4093
|
-
if (message.includes("404")) {
|
|
4094
|
-
return {
|
|
4095
|
-
content: [
|
|
4096
|
-
{
|
|
4097
|
-
type: "text",
|
|
4098
|
-
text: "No carousel template has been configured yet. Use `save_carousel_template` to set one up."
|
|
4099
|
-
}
|
|
4100
|
-
]
|
|
4101
|
-
};
|
|
4102
|
-
}
|
|
4103
|
-
throw error;
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
);
|
|
4107
|
-
server.tool(
|
|
4108
|
-
"save_carousel_template",
|
|
4109
|
-
`Create or update the customer's carousel template. This configures the visual style for auto-generated carousels (brand colors, logo, CTA text).
|
|
4110
|
-
|
|
4111
|
-
USAGE:
|
|
4112
|
-
- Set confirmed=false first to preview, then confirmed=true after user approval
|
|
4113
|
-
- brand_name is required
|
|
4114
|
-
- All color values should be hex codes (e.g. "#1a1a2e")
|
|
4115
|
-
- logo_placement: "top-left", "top-right", or "top-center"`,
|
|
4116
|
-
{
|
|
4117
|
-
brand_name: z16.string().max(255).describe("Brand name displayed on CTA slide"),
|
|
4118
|
-
color_primary: z16.string().max(20).optional().describe('Primary background color (hex, e.g. "#1a1a2e")'),
|
|
4119
|
-
color_secondary: z16.string().max(20).optional().describe('Secondary/accent color for buttons and highlights (hex, e.g. "#e94560")'),
|
|
4120
|
-
color_accent: z16.string().max(20).optional().describe('Text color (hex, e.g. "#ffffff")'),
|
|
4121
|
-
cta_text: z16.string().max(255).optional().describe('Call-to-action text on the final slide (e.g. "Read the full story \u2192")'),
|
|
4122
|
-
logo_placement: z16.enum(["top-left", "top-right", "top-center"]).optional().describe("Where to place the logo on slides"),
|
|
4123
|
-
confirmed: z16.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
4124
|
-
},
|
|
4125
|
-
{
|
|
4126
|
-
title: "Save Carousel Template",
|
|
4127
|
-
readOnlyHint: false,
|
|
4128
|
-
destructiveHint: false,
|
|
4129
|
-
idempotentHint: false,
|
|
4130
|
-
openWorldHint: false
|
|
4131
|
-
},
|
|
4132
|
-
async (args) => {
|
|
4133
|
-
const payload = {
|
|
4134
|
-
brandName: args.brand_name
|
|
4135
|
-
};
|
|
4136
|
-
if (args.color_primary !== void 0) payload.colorPrimary = args.color_primary;
|
|
4137
|
-
if (args.color_secondary !== void 0) payload.colorSecondary = args.color_secondary;
|
|
4138
|
-
if (args.color_accent !== void 0) payload.colorAccent = args.color_accent;
|
|
4139
|
-
if (args.cta_text !== void 0) payload.ctaText = args.cta_text;
|
|
4140
|
-
if (args.logo_placement !== void 0) payload.logoPlacement = args.logo_placement;
|
|
4141
|
-
if (args.confirmed !== true) {
|
|
4142
|
-
const lines2 = [];
|
|
4143
|
-
lines2.push("## Carousel Template Preview");
|
|
4144
|
-
lines2.push("");
|
|
4145
|
-
lines2.push(`**Brand Name:** ${args.brand_name}`);
|
|
4146
|
-
if (args.color_primary) lines2.push(`**Primary Color:** ${args.color_primary}`);
|
|
4147
|
-
if (args.color_secondary) lines2.push(`**Secondary Color:** ${args.color_secondary}`);
|
|
4148
|
-
if (args.color_accent) lines2.push(`**Accent Color:** ${args.color_accent}`);
|
|
4149
|
-
if (args.cta_text) lines2.push(`**CTA Text:** ${args.cta_text}`);
|
|
4150
|
-
if (args.logo_placement) lines2.push(`**Logo Placement:** ${args.logo_placement}`);
|
|
4151
|
-
lines2.push("");
|
|
4152
|
-
lines2.push("---");
|
|
4153
|
-
lines2.push("Call this tool again with **confirmed=true** to save these settings.");
|
|
4154
|
-
return {
|
|
4155
|
-
content: [{ type: "text", text: lines2.join("\n") }]
|
|
4156
|
-
};
|
|
4157
|
-
}
|
|
4158
|
-
const result = await client.updateCarouselTemplate(payload);
|
|
4159
|
-
const lines = [];
|
|
4160
|
-
lines.push(`Carousel template "${result.brandName || args.brand_name}" has been saved successfully.`);
|
|
4161
|
-
lines.push("");
|
|
4162
|
-
const updated = ["brand name"];
|
|
4163
|
-
if (args.color_primary) updated.push("primary color");
|
|
4164
|
-
if (args.color_secondary) updated.push("secondary color");
|
|
4165
|
-
if (args.color_accent) updated.push("accent color");
|
|
4166
|
-
if (args.cta_text) updated.push("CTA text");
|
|
4167
|
-
if (args.logo_placement) updated.push("logo placement");
|
|
4168
|
-
lines.push(`**Updated:** ${updated.join(", ")}`);
|
|
4169
|
-
return {
|
|
4170
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4171
|
-
};
|
|
4172
|
-
}
|
|
4173
|
-
);
|
|
4174
|
-
server.tool(
|
|
4175
|
-
"generate_carousel",
|
|
4176
|
-
"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.",
|
|
4177
|
-
{
|
|
4178
|
-
url: z16.string().url().describe("The article URL to generate a carousel from")
|
|
4179
|
-
},
|
|
4180
|
-
{
|
|
4181
|
-
title: "Generate Carousel",
|
|
4182
|
-
readOnlyHint: false,
|
|
4183
|
-
destructiveHint: false,
|
|
4184
|
-
idempotentHint: false,
|
|
4185
|
-
openWorldHint: true
|
|
4186
|
-
},
|
|
4187
|
-
async (args) => {
|
|
4188
|
-
try {
|
|
4189
|
-
const result = await client.generateCarousel({
|
|
4190
|
-
url: args.url
|
|
4191
|
-
});
|
|
4192
|
-
const lines = [];
|
|
4193
|
-
lines.push("## Carousel Generated");
|
|
4194
|
-
lines.push("");
|
|
4195
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4196
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4197
|
-
lines.push(`**Slides:** ${result.slides.length}`);
|
|
4198
|
-
lines.push("");
|
|
4199
|
-
lines.push("### Key Points");
|
|
4200
|
-
for (const point of result.article.keyPoints) {
|
|
4201
|
-
lines.push(`- ${point}`);
|
|
4202
|
-
}
|
|
4203
|
-
lines.push("");
|
|
4204
|
-
lines.push("### Slide URLs");
|
|
4205
|
-
for (const slide of result.slides) {
|
|
4206
|
-
lines.push(`${slide.slideNumber}. [${slide.type}](${slide.cdnUrl})`);
|
|
4207
|
-
}
|
|
4208
|
-
lines.push("");
|
|
4209
|
-
lines.push("These URLs can be used as `media_urls` when creating a post.");
|
|
4210
|
-
return {
|
|
4211
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4212
|
-
};
|
|
4213
|
-
} catch (error) {
|
|
4214
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4215
|
-
return {
|
|
4216
|
-
content: [
|
|
4217
|
-
{
|
|
4218
|
-
type: "text",
|
|
4219
|
-
text: `Failed to generate carousel: ${message}`
|
|
4220
|
-
}
|
|
4221
|
-
],
|
|
4222
|
-
isError: true
|
|
4223
|
-
};
|
|
4224
|
-
}
|
|
4225
|
-
}
|
|
4226
|
-
);
|
|
4227
|
-
server.tool(
|
|
4228
|
-
"preview_carousel",
|
|
4229
|
-
"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.",
|
|
4230
|
-
{
|
|
4231
|
-
url: z16.string().url().describe("The article URL to preview")
|
|
4232
|
-
},
|
|
4233
|
-
{
|
|
4234
|
-
title: "Preview Carousel",
|
|
4235
|
-
readOnlyHint: true,
|
|
4236
|
-
destructiveHint: false,
|
|
4237
|
-
idempotentHint: false,
|
|
4238
|
-
openWorldHint: true
|
|
4239
|
-
},
|
|
4240
|
-
async (args) => {
|
|
4241
|
-
try {
|
|
4242
|
-
const result = await client.previewCarousel({
|
|
4243
|
-
url: args.url
|
|
4244
|
-
});
|
|
4245
|
-
const lines = [];
|
|
4246
|
-
lines.push("## Carousel Preview");
|
|
4247
|
-
lines.push("");
|
|
4248
|
-
lines.push(`**Headline:** ${result.article.headline}`);
|
|
4249
|
-
lines.push(`**Source:** ${result.article.sourceDomain}`);
|
|
4250
|
-
lines.push("");
|
|
4251
|
-
lines.push(`**Hero Slide:** ${result.cdnUrl}`);
|
|
4252
|
-
lines.push("");
|
|
4253
|
-
lines.push("Use `generate_carousel` to create the full carousel with all slides.");
|
|
4254
|
-
return {
|
|
4255
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
4256
|
-
};
|
|
4257
|
-
} catch (error) {
|
|
4258
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4259
|
-
return {
|
|
4260
|
-
content: [
|
|
4261
|
-
{
|
|
4262
|
-
type: "text",
|
|
4263
|
-
text: `Failed to preview carousel: ${message}`
|
|
4264
|
-
}
|
|
4265
|
-
],
|
|
4266
|
-
isError: true
|
|
4267
|
-
};
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
);
|
|
4271
|
-
}
|
|
4272
3313
|
export {
|
|
4273
3314
|
BuzzPosterClient,
|
|
4274
3315
|
registerAccountInfoTool,
|
|
4275
3316
|
registerAccountTools,
|
|
4276
3317
|
registerAnalyticsTools,
|
|
4277
3318
|
registerAudienceTools,
|
|
4278
|
-
registerAuditLogTools,
|
|
4279
3319
|
registerBrandVoiceTools,
|
|
4280
3320
|
registerCalendarTools,
|
|
4281
|
-
registerCarouselTools,
|
|
4282
3321
|
registerInboxTools,
|
|
4283
3322
|
registerKnowledgeTools,
|
|
4284
3323
|
registerMediaTools,
|