@buzzposter/mcp 0.1.1 → 0.1.4
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 +1159 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -163,6 +163,53 @@ var BuzzPosterClient = class {
|
|
|
163
163
|
async getBrandVoice() {
|
|
164
164
|
return this.request("GET", "/api/v1/brand-voice");
|
|
165
165
|
}
|
|
166
|
+
// Audiences
|
|
167
|
+
async getAudience(id) {
|
|
168
|
+
return this.request("GET", `/api/v1/audiences/${id}`);
|
|
169
|
+
}
|
|
170
|
+
async getDefaultAudience() {
|
|
171
|
+
return this.request("GET", "/api/v1/audiences/default");
|
|
172
|
+
}
|
|
173
|
+
// Newsletter Templates
|
|
174
|
+
async getTemplate(id) {
|
|
175
|
+
if (id) return this.request("GET", `/api/v1/templates/${id}`);
|
|
176
|
+
return this.request("GET", "/api/v1/templates/default");
|
|
177
|
+
}
|
|
178
|
+
async listTemplates() {
|
|
179
|
+
return this.request("GET", "/api/v1/templates");
|
|
180
|
+
}
|
|
181
|
+
// Newsletter Archive
|
|
182
|
+
async listNewsletterArchive(params) {
|
|
183
|
+
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
184
|
+
}
|
|
185
|
+
async getArchivedNewsletter(id) {
|
|
186
|
+
return this.request("GET", `/api/v1/newsletter-archive/${id}`);
|
|
187
|
+
}
|
|
188
|
+
async saveNewsletterToArchive(data) {
|
|
189
|
+
return this.request("POST", "/api/v1/newsletter-archive", data);
|
|
190
|
+
}
|
|
191
|
+
// Calendar
|
|
192
|
+
async getCalendar(params) {
|
|
193
|
+
return this.request("GET", "/api/v1/calendar", void 0, params);
|
|
194
|
+
}
|
|
195
|
+
async rescheduleCalendarItem(id, scheduledFor) {
|
|
196
|
+
return this.request("PUT", `/api/v1/calendar/${id}`, {
|
|
197
|
+
scheduled_for: scheduledFor
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async cancelCalendarItem(id) {
|
|
201
|
+
return this.request("DELETE", `/api/v1/calendar/${id}`);
|
|
202
|
+
}
|
|
203
|
+
// Queue
|
|
204
|
+
async getQueue() {
|
|
205
|
+
return this.request("GET", "/api/v1/queue");
|
|
206
|
+
}
|
|
207
|
+
async updateQueue(data) {
|
|
208
|
+
return this.request("PUT", "/api/v1/queue", data);
|
|
209
|
+
}
|
|
210
|
+
async getNextSlot() {
|
|
211
|
+
return this.request("GET", "/api/v1/queue/next");
|
|
212
|
+
}
|
|
166
213
|
// RSS
|
|
167
214
|
async fetchFeed(url, limit) {
|
|
168
215
|
const params = { url };
|
|
@@ -172,6 +219,16 @@ var BuzzPosterClient = class {
|
|
|
172
219
|
async fetchArticle(url) {
|
|
173
220
|
return this.request("GET", "/api/v1/rss/article", void 0, { url });
|
|
174
221
|
}
|
|
222
|
+
// Notifications
|
|
223
|
+
async getNotifications(params) {
|
|
224
|
+
return this.request("GET", "/api/v1/notifications", void 0, params);
|
|
225
|
+
}
|
|
226
|
+
async markNotificationRead(id) {
|
|
227
|
+
return this.request("PUT", `/api/v1/notifications/${id}/read`);
|
|
228
|
+
}
|
|
229
|
+
async markAllNotificationsRead() {
|
|
230
|
+
return this.request("PUT", "/api/v1/notifications/read-all");
|
|
231
|
+
}
|
|
175
232
|
};
|
|
176
233
|
|
|
177
234
|
// src/tools/posts.ts
|
|
@@ -179,16 +236,36 @@ import { z } from "zod";
|
|
|
179
236
|
function registerPostTools(server2, client2) {
|
|
180
237
|
server2.tool(
|
|
181
238
|
"post",
|
|
182
|
-
"Create and publish a post to one or more social media platforms. Supports Twitter, Instagram, LinkedIn, and Facebook with full platform-specific options.",
|
|
239
|
+
"Create and publish a post to one or more social media platforms. Supports Twitter, Instagram, LinkedIn, and Facebook with full platform-specific options. Requires confirmation before publishing.",
|
|
183
240
|
{
|
|
184
241
|
content: z.string().optional().describe("The text content of the post"),
|
|
185
242
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
186
243
|
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
187
244
|
platform_specific: z.record(z.record(z.unknown())).optional().describe(
|
|
188
245
|
"Platform-specific data keyed by platform name. E.g. { twitter: { threadItems: [...] }, instagram: { firstComment: '...' } }"
|
|
246
|
+
),
|
|
247
|
+
confirmed: z.boolean().optional().describe(
|
|
248
|
+
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
189
249
|
)
|
|
190
250
|
},
|
|
251
|
+
{
|
|
252
|
+
title: "Publish Post Now",
|
|
253
|
+
readOnlyHint: false,
|
|
254
|
+
destructiveHint: false,
|
|
255
|
+
idempotentHint: false,
|
|
256
|
+
openWorldHint: true
|
|
257
|
+
},
|
|
191
258
|
async (args) => {
|
|
259
|
+
if (!args.confirmed) {
|
|
260
|
+
const preview = `## Post Preview
|
|
261
|
+
|
|
262
|
+
**Content:** "${args.content ?? "(no text)"}"
|
|
263
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
264
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
265
|
+
` : "") + `
|
|
266
|
+
This will be published **immediately**. Call this tool again with confirmed=true to proceed.`;
|
|
267
|
+
return { content: [{ type: "text", text: preview }] };
|
|
268
|
+
}
|
|
192
269
|
const platforms = args.platforms.map((platform) => {
|
|
193
270
|
const entry = { platform };
|
|
194
271
|
if (args.platform_specific?.[platform]) {
|
|
@@ -215,10 +292,20 @@ function registerPostTools(server2, client2) {
|
|
|
215
292
|
);
|
|
216
293
|
server2.tool(
|
|
217
294
|
"cross_post",
|
|
218
|
-
"Post the same content to all connected social media platforms at once.",
|
|
295
|
+
"Post the same content to all connected social media platforms at once. Requires confirmation before publishing.",
|
|
219
296
|
{
|
|
220
297
|
content: z.string().describe("The text content to post everywhere"),
|
|
221
|
-
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach")
|
|
298
|
+
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
299
|
+
confirmed: z.boolean().optional().describe(
|
|
300
|
+
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
301
|
+
)
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
title: "Cross-Post to All Platforms",
|
|
305
|
+
readOnlyHint: false,
|
|
306
|
+
destructiveHint: false,
|
|
307
|
+
idempotentHint: false,
|
|
308
|
+
openWorldHint: true
|
|
222
309
|
},
|
|
223
310
|
async (args) => {
|
|
224
311
|
const accountsData = await client2.listAccounts();
|
|
@@ -235,6 +322,16 @@ function registerPostTools(server2, client2) {
|
|
|
235
322
|
]
|
|
236
323
|
};
|
|
237
324
|
}
|
|
325
|
+
if (!args.confirmed) {
|
|
326
|
+
const preview = `## Cross-Post Preview
|
|
327
|
+
|
|
328
|
+
**Content:** "${args.content}"
|
|
329
|
+
**Platforms:** ${platforms.join(", ")}
|
|
330
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
331
|
+
` : "") + `
|
|
332
|
+
This will be published **immediately** to all connected platforms. Call this tool again with confirmed=true to proceed.`;
|
|
333
|
+
return { content: [{ type: "text", text: preview }] };
|
|
334
|
+
}
|
|
238
335
|
const body = {
|
|
239
336
|
content: args.content,
|
|
240
337
|
platforms: platforms.map((p) => ({ platform: p })),
|
|
@@ -254,16 +351,37 @@ function registerPostTools(server2, client2) {
|
|
|
254
351
|
);
|
|
255
352
|
server2.tool(
|
|
256
353
|
"schedule_post",
|
|
257
|
-
"Schedule a post for future publication.",
|
|
354
|
+
"Schedule a post for future publication. Requires confirmation before scheduling.",
|
|
258
355
|
{
|
|
259
356
|
content: z.string().optional().describe("The text content of the post"),
|
|
260
357
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
261
358
|
scheduled_for: z.string().describe("ISO 8601 datetime for when to publish, e.g. 2024-01-16T12:00:00"),
|
|
262
359
|
timezone: z.string().default("UTC").describe("Timezone for the scheduled time, e.g. America/New_York"),
|
|
263
360
|
media_urls: z.array(z.string()).optional().describe("Media URLs to attach"),
|
|
264
|
-
platform_specific: z.record(z.record(z.unknown())).optional().describe("Platform-specific data keyed by platform name")
|
|
361
|
+
platform_specific: z.record(z.record(z.unknown())).optional().describe("Platform-specific data keyed by platform name"),
|
|
362
|
+
confirmed: z.boolean().optional().describe(
|
|
363
|
+
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
364
|
+
)
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
title: "Schedule Social Post",
|
|
368
|
+
readOnlyHint: false,
|
|
369
|
+
destructiveHint: false,
|
|
370
|
+
idempotentHint: false,
|
|
371
|
+
openWorldHint: true
|
|
265
372
|
},
|
|
266
373
|
async (args) => {
|
|
374
|
+
if (!args.confirmed) {
|
|
375
|
+
const preview = `## Schedule Preview
|
|
376
|
+
|
|
377
|
+
**Content:** "${args.content ?? "(no text)"}"
|
|
378
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
379
|
+
**Scheduled for:** ${args.scheduled_for} (${args.timezone})
|
|
380
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
381
|
+
` : "") + `
|
|
382
|
+
Call this tool again with confirmed=true to schedule.`;
|
|
383
|
+
return { content: [{ type: "text", text: preview }] };
|
|
384
|
+
}
|
|
267
385
|
const platforms = args.platforms.map((platform) => {
|
|
268
386
|
const entry = { platform };
|
|
269
387
|
if (args.platform_specific?.[platform]) {
|
|
@@ -297,6 +415,13 @@ function registerPostTools(server2, client2) {
|
|
|
297
415
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
298
416
|
media_urls: z.array(z.string()).optional().describe("Media URLs to attach")
|
|
299
417
|
},
|
|
418
|
+
{
|
|
419
|
+
title: "Create Draft Post",
|
|
420
|
+
readOnlyHint: false,
|
|
421
|
+
destructiveHint: false,
|
|
422
|
+
idempotentHint: false,
|
|
423
|
+
openWorldHint: false
|
|
424
|
+
},
|
|
300
425
|
async (args) => {
|
|
301
426
|
const body = {
|
|
302
427
|
content: args.content,
|
|
@@ -322,6 +447,13 @@ function registerPostTools(server2, client2) {
|
|
|
322
447
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
323
448
|
limit: z.string().optional().describe("Number of posts to return")
|
|
324
449
|
},
|
|
450
|
+
{
|
|
451
|
+
title: "List Posts",
|
|
452
|
+
readOnlyHint: true,
|
|
453
|
+
destructiveHint: false,
|
|
454
|
+
idempotentHint: true,
|
|
455
|
+
openWorldHint: true
|
|
456
|
+
},
|
|
325
457
|
async (args) => {
|
|
326
458
|
const params = {};
|
|
327
459
|
if (args.status) params.status = args.status;
|
|
@@ -338,6 +470,13 @@ function registerPostTools(server2, client2) {
|
|
|
338
470
|
{
|
|
339
471
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
340
472
|
},
|
|
473
|
+
{
|
|
474
|
+
title: "Get Post Details",
|
|
475
|
+
readOnlyHint: true,
|
|
476
|
+
destructiveHint: false,
|
|
477
|
+
idempotentHint: true,
|
|
478
|
+
openWorldHint: true
|
|
479
|
+
},
|
|
341
480
|
async (args) => {
|
|
342
481
|
const result = await client2.getPost(args.post_id);
|
|
343
482
|
return {
|
|
@@ -347,11 +486,48 @@ function registerPostTools(server2, client2) {
|
|
|
347
486
|
);
|
|
348
487
|
server2.tool(
|
|
349
488
|
"retry_post",
|
|
350
|
-
"Retry
|
|
489
|
+
"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.",
|
|
351
490
|
{
|
|
352
|
-
post_id: z.string().describe("The ID of the post to retry")
|
|
491
|
+
post_id: z.string().describe("The ID of the post to retry"),
|
|
492
|
+
confirmed: z.boolean().optional().describe(
|
|
493
|
+
"Set to true to confirm retry. If false/missing, shows post details first."
|
|
494
|
+
)
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
title: "Retry Failed Post",
|
|
498
|
+
readOnlyHint: false,
|
|
499
|
+
destructiveHint: false,
|
|
500
|
+
idempotentHint: true,
|
|
501
|
+
openWorldHint: true
|
|
353
502
|
},
|
|
354
503
|
async (args) => {
|
|
504
|
+
if (!args.confirmed) {
|
|
505
|
+
const post = await client2.getPost(args.post_id);
|
|
506
|
+
const platforms = post.platforms ?? [];
|
|
507
|
+
const failed = platforms.filter((p) => p.status === "failed");
|
|
508
|
+
const published = platforms.filter((p) => p.status === "published");
|
|
509
|
+
let text = `## Retry Post Preview
|
|
510
|
+
|
|
511
|
+
`;
|
|
512
|
+
text += `**Post ID:** ${args.post_id}
|
|
513
|
+
`;
|
|
514
|
+
text += `**Content:** "${(post.content ?? "").substring(0, 100)}"
|
|
515
|
+
`;
|
|
516
|
+
text += `**Status:** ${post.status}
|
|
517
|
+
|
|
518
|
+
`;
|
|
519
|
+
if (published.length > 0) {
|
|
520
|
+
text += `**Already published on:** ${published.map((p) => p.platform).join(", ")}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
if (failed.length > 0) {
|
|
524
|
+
text += `**Failed on:** ${failed.map((p) => `${p.platform} (${p.error ?? "unknown error"})`).join(", ")}
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
527
|
+
text += `
|
|
528
|
+
Retrying will only attempt the failed platforms. Call this tool again with confirmed=true to proceed.`;
|
|
529
|
+
return { content: [{ type: "text", text }] };
|
|
530
|
+
}
|
|
355
531
|
const result = await client2.retryPost(args.post_id);
|
|
356
532
|
return {
|
|
357
533
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -366,6 +542,13 @@ function registerAccountTools(server2, client2) {
|
|
|
366
542
|
"list_accounts",
|
|
367
543
|
"List all connected social media accounts with their platform, username, and connection status.",
|
|
368
544
|
{},
|
|
545
|
+
{
|
|
546
|
+
title: "List Connected Accounts",
|
|
547
|
+
readOnlyHint: true,
|
|
548
|
+
destructiveHint: false,
|
|
549
|
+
idempotentHint: true,
|
|
550
|
+
openWorldHint: false
|
|
551
|
+
},
|
|
369
552
|
async () => {
|
|
370
553
|
const result = await client2.listAccounts();
|
|
371
554
|
return {
|
|
@@ -375,12 +558,49 @@ function registerAccountTools(server2, client2) {
|
|
|
375
558
|
);
|
|
376
559
|
server2.tool(
|
|
377
560
|
"check_accounts_health",
|
|
378
|
-
"Check the health
|
|
561
|
+
"Check the health status of all connected social media accounts. Shows which accounts are working, which have warnings, and which need reconnection. Call this before scheduling posts to make sure target platforms are healthy.",
|
|
379
562
|
{},
|
|
563
|
+
{
|
|
564
|
+
title: "Check Account Health",
|
|
565
|
+
readOnlyHint: true,
|
|
566
|
+
destructiveHint: false,
|
|
567
|
+
idempotentHint: true,
|
|
568
|
+
openWorldHint: true
|
|
569
|
+
},
|
|
380
570
|
async () => {
|
|
381
571
|
const result = await client2.checkAccountsHealth();
|
|
572
|
+
const accounts = result.accounts ?? [];
|
|
573
|
+
const summary = result.summary;
|
|
574
|
+
if (accounts.length === 0) {
|
|
575
|
+
return {
|
|
576
|
+
content: [{
|
|
577
|
+
type: "text",
|
|
578
|
+
text: "## Account Health\n\nNo connected accounts found. Connect accounts first via the dashboard."
|
|
579
|
+
}]
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const statusIcon = {
|
|
583
|
+
healthy: "\u2705",
|
|
584
|
+
warning: "\u26A0\uFE0F",
|
|
585
|
+
error: "\u274C"
|
|
586
|
+
};
|
|
587
|
+
const lines = accounts.map((a) => {
|
|
588
|
+
const icon = statusIcon[a.status] ?? "\u2753";
|
|
589
|
+
const name = a.username ? `@${a.username}` : a.platform;
|
|
590
|
+
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
591
|
+
const msg = a.message || (a.status === "healthy" ? "Healthy" : a.status);
|
|
592
|
+
return `${icon} **${platform}** (${name}) \u2014 ${msg}`;
|
|
593
|
+
});
|
|
594
|
+
let text = `## Account Health
|
|
595
|
+
|
|
596
|
+
${lines.join("\n")}`;
|
|
597
|
+
if (summary) {
|
|
598
|
+
text += `
|
|
599
|
+
|
|
600
|
+
**Summary:** ${summary.total} total, ${summary.healthy} healthy, ${summary.warning} warning, ${summary.error} error`;
|
|
601
|
+
}
|
|
382
602
|
return {
|
|
383
|
-
content: [{ type: "text", text
|
|
603
|
+
content: [{ type: "text", text }]
|
|
384
604
|
};
|
|
385
605
|
}
|
|
386
606
|
);
|
|
@@ -397,6 +617,13 @@ function registerAnalyticsTools(server2, client2) {
|
|
|
397
617
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
398
618
|
to_date: z2.string().optional().describe("End date for analytics range (ISO 8601)")
|
|
399
619
|
},
|
|
620
|
+
{
|
|
621
|
+
title: "Get Analytics",
|
|
622
|
+
readOnlyHint: true,
|
|
623
|
+
destructiveHint: false,
|
|
624
|
+
idempotentHint: true,
|
|
625
|
+
openWorldHint: true
|
|
626
|
+
},
|
|
400
627
|
async (args) => {
|
|
401
628
|
const params = {};
|
|
402
629
|
if (args.platform) params.platform = args.platform;
|
|
@@ -419,6 +646,13 @@ function registerInboxTools(server2, client2) {
|
|
|
419
646
|
{
|
|
420
647
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
421
648
|
},
|
|
649
|
+
{
|
|
650
|
+
title: "List Conversations",
|
|
651
|
+
readOnlyHint: true,
|
|
652
|
+
destructiveHint: false,
|
|
653
|
+
idempotentHint: true,
|
|
654
|
+
openWorldHint: true
|
|
655
|
+
},
|
|
422
656
|
async (args) => {
|
|
423
657
|
const params = {};
|
|
424
658
|
if (args.platform) params.platform = args.platform;
|
|
@@ -434,6 +668,13 @@ function registerInboxTools(server2, client2) {
|
|
|
434
668
|
{
|
|
435
669
|
conversation_id: z3.string().describe("The conversation ID")
|
|
436
670
|
},
|
|
671
|
+
{
|
|
672
|
+
title: "Get Conversation Messages",
|
|
673
|
+
readOnlyHint: true,
|
|
674
|
+
destructiveHint: false,
|
|
675
|
+
idempotentHint: true,
|
|
676
|
+
openWorldHint: true
|
|
677
|
+
},
|
|
437
678
|
async (args) => {
|
|
438
679
|
const result = await client2.getConversation(args.conversation_id);
|
|
439
680
|
return {
|
|
@@ -443,12 +684,29 @@ function registerInboxTools(server2, client2) {
|
|
|
443
684
|
);
|
|
444
685
|
server2.tool(
|
|
445
686
|
"reply_to_conversation",
|
|
446
|
-
"Send a reply message in a DM conversation.",
|
|
687
|
+
"Send a reply message in a DM conversation. Sends a message to an external platform on your behalf.",
|
|
447
688
|
{
|
|
448
689
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
449
|
-
message: z3.string().describe("The reply message text")
|
|
690
|
+
message: z3.string().describe("The reply message text"),
|
|
691
|
+
confirmed: z3.boolean().optional().describe("Set to true to confirm and send. If false or missing, returns a preview for user approval.")
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
title: "Reply to Conversation",
|
|
695
|
+
readOnlyHint: false,
|
|
696
|
+
destructiveHint: false,
|
|
697
|
+
idempotentHint: false,
|
|
698
|
+
openWorldHint: true
|
|
450
699
|
},
|
|
451
700
|
async (args) => {
|
|
701
|
+
if (!args.confirmed) {
|
|
702
|
+
const preview = `## Reply Preview
|
|
703
|
+
|
|
704
|
+
**Conversation:** ${args.conversation_id}
|
|
705
|
+
**Message:** "${args.message}"
|
|
706
|
+
|
|
707
|
+
This will send a DM reply on the external platform. Call this tool again with confirmed=true to send.`;
|
|
708
|
+
return { content: [{ type: "text", text: preview }] };
|
|
709
|
+
}
|
|
452
710
|
const result = await client2.replyToConversation(
|
|
453
711
|
args.conversation_id,
|
|
454
712
|
args.message
|
|
@@ -464,6 +722,13 @@ function registerInboxTools(server2, client2) {
|
|
|
464
722
|
{
|
|
465
723
|
post_id: z3.string().optional().describe("Filter comments by post ID")
|
|
466
724
|
},
|
|
725
|
+
{
|
|
726
|
+
title: "List Comments",
|
|
727
|
+
readOnlyHint: true,
|
|
728
|
+
destructiveHint: false,
|
|
729
|
+
idempotentHint: true,
|
|
730
|
+
openWorldHint: true
|
|
731
|
+
},
|
|
467
732
|
async (args) => {
|
|
468
733
|
const params = {};
|
|
469
734
|
if (args.post_id) params.postId = args.post_id;
|
|
@@ -475,12 +740,29 @@ function registerInboxTools(server2, client2) {
|
|
|
475
740
|
);
|
|
476
741
|
server2.tool(
|
|
477
742
|
"reply_to_comment",
|
|
478
|
-
"Reply to a comment on one of your posts.",
|
|
743
|
+
"Reply to a comment on one of your posts. Sends a reply on the external platform.",
|
|
479
744
|
{
|
|
480
745
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
481
|
-
message: z3.string().describe("The reply text")
|
|
746
|
+
message: z3.string().describe("The reply text"),
|
|
747
|
+
confirmed: z3.boolean().optional().describe("Set to true to confirm and send. If false or missing, returns a preview for user approval.")
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
title: "Reply to Comment",
|
|
751
|
+
readOnlyHint: false,
|
|
752
|
+
destructiveHint: false,
|
|
753
|
+
idempotentHint: false,
|
|
754
|
+
openWorldHint: true
|
|
482
755
|
},
|
|
483
756
|
async (args) => {
|
|
757
|
+
if (!args.confirmed) {
|
|
758
|
+
const preview = `## Comment Reply Preview
|
|
759
|
+
|
|
760
|
+
**Comment ID:** ${args.comment_id}
|
|
761
|
+
**Reply:** "${args.message}"
|
|
762
|
+
|
|
763
|
+
This will post a public reply. Call this tool again with confirmed=true to send.`;
|
|
764
|
+
return { content: [{ type: "text", text: preview }] };
|
|
765
|
+
}
|
|
484
766
|
const result = await client2.replyToComment(
|
|
485
767
|
args.comment_id,
|
|
486
768
|
args.message
|
|
@@ -494,6 +776,13 @@ function registerInboxTools(server2, client2) {
|
|
|
494
776
|
"list_reviews",
|
|
495
777
|
"List reviews on your Facebook page.",
|
|
496
778
|
{},
|
|
779
|
+
{
|
|
780
|
+
title: "List Reviews",
|
|
781
|
+
readOnlyHint: true,
|
|
782
|
+
destructiveHint: false,
|
|
783
|
+
idempotentHint: true,
|
|
784
|
+
openWorldHint: true
|
|
785
|
+
},
|
|
497
786
|
async () => {
|
|
498
787
|
const result = await client2.listReviews();
|
|
499
788
|
return {
|
|
@@ -503,12 +792,29 @@ function registerInboxTools(server2, client2) {
|
|
|
503
792
|
);
|
|
504
793
|
server2.tool(
|
|
505
794
|
"reply_to_review",
|
|
506
|
-
"Reply to a review on your Facebook page.",
|
|
795
|
+
"Reply to a review on your Facebook page. Sends a public reply on the external platform.",
|
|
507
796
|
{
|
|
508
797
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
509
|
-
message: z3.string().describe("The reply text")
|
|
798
|
+
message: z3.string().describe("The reply text"),
|
|
799
|
+
confirmed: z3.boolean().optional().describe("Set to true to confirm and send. If false or missing, returns a preview for user approval.")
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
title: "Reply to Review",
|
|
803
|
+
readOnlyHint: false,
|
|
804
|
+
destructiveHint: false,
|
|
805
|
+
idempotentHint: false,
|
|
806
|
+
openWorldHint: true
|
|
510
807
|
},
|
|
511
808
|
async (args) => {
|
|
809
|
+
if (!args.confirmed) {
|
|
810
|
+
const preview = `## Review Reply Preview
|
|
811
|
+
|
|
812
|
+
**Review ID:** ${args.review_id}
|
|
813
|
+
**Reply:** "${args.message}"
|
|
814
|
+
|
|
815
|
+
This will post a public reply to the review. Call this tool again with confirmed=true to send.`;
|
|
816
|
+
return { content: [{ type: "text", text: preview }] };
|
|
817
|
+
}
|
|
512
818
|
const result = await client2.replyToReview(
|
|
513
819
|
args.review_id,
|
|
514
820
|
args.message
|
|
@@ -530,6 +836,13 @@ function registerMediaTools(server2, client2) {
|
|
|
530
836
|
{
|
|
531
837
|
file_path: z4.string().describe("Absolute path to the file on the local filesystem")
|
|
532
838
|
},
|
|
839
|
+
{
|
|
840
|
+
title: "Upload Media File",
|
|
841
|
+
readOnlyHint: false,
|
|
842
|
+
destructiveHint: false,
|
|
843
|
+
idempotentHint: false,
|
|
844
|
+
openWorldHint: false
|
|
845
|
+
},
|
|
533
846
|
async (args) => {
|
|
534
847
|
const buffer = await readFile(args.file_path);
|
|
535
848
|
const base64 = buffer.toString("base64");
|
|
@@ -562,6 +875,13 @@ function registerMediaTools(server2, client2) {
|
|
|
562
875
|
data: z4.string().describe("Base64-encoded file data"),
|
|
563
876
|
mime_type: z4.string().describe("MIME type of the file, e.g. image/jpeg, video/mp4")
|
|
564
877
|
},
|
|
878
|
+
{
|
|
879
|
+
title: "Upload Media Base64",
|
|
880
|
+
readOnlyHint: false,
|
|
881
|
+
destructiveHint: false,
|
|
882
|
+
idempotentHint: false,
|
|
883
|
+
openWorldHint: false
|
|
884
|
+
},
|
|
565
885
|
async (args) => {
|
|
566
886
|
const result = await client2.uploadMedia(
|
|
567
887
|
args.filename,
|
|
@@ -577,6 +897,13 @@ function registerMediaTools(server2, client2) {
|
|
|
577
897
|
"list_media",
|
|
578
898
|
"List all uploaded media files in your BuzzPoster media library.",
|
|
579
899
|
{},
|
|
900
|
+
{
|
|
901
|
+
title: "List Media Library",
|
|
902
|
+
readOnlyHint: true,
|
|
903
|
+
destructiveHint: false,
|
|
904
|
+
idempotentHint: true,
|
|
905
|
+
openWorldHint: false
|
|
906
|
+
},
|
|
580
907
|
async () => {
|
|
581
908
|
const result = await client2.listMedia();
|
|
582
909
|
return {
|
|
@@ -586,11 +913,27 @@ function registerMediaTools(server2, client2) {
|
|
|
586
913
|
);
|
|
587
914
|
server2.tool(
|
|
588
915
|
"delete_media",
|
|
589
|
-
"Delete a media file from your BuzzPoster media library.",
|
|
916
|
+
"Delete a media file from your BuzzPoster media library. This cannot be undone.",
|
|
590
917
|
{
|
|
591
|
-
key: z4.string().describe("The key/path of the media file to delete")
|
|
918
|
+
key: z4.string().describe("The key/path of the media file to delete"),
|
|
919
|
+
confirmed: z4.boolean().optional().describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
title: "Delete Media File",
|
|
923
|
+
readOnlyHint: false,
|
|
924
|
+
destructiveHint: true,
|
|
925
|
+
idempotentHint: true,
|
|
926
|
+
openWorldHint: false
|
|
592
927
|
},
|
|
593
928
|
async (args) => {
|
|
929
|
+
if (!args.confirmed) {
|
|
930
|
+
const preview = `## Delete Media Confirmation
|
|
931
|
+
|
|
932
|
+
**File:** ${args.key}
|
|
933
|
+
|
|
934
|
+
This will permanently delete this media file. Call this tool again with confirmed=true to proceed.`;
|
|
935
|
+
return { content: [{ type: "text", text: preview }] };
|
|
936
|
+
}
|
|
594
937
|
await client2.deleteMedia(args.key);
|
|
595
938
|
return {
|
|
596
939
|
content: [{ type: "text", text: "Media deleted successfully." }]
|
|
@@ -609,6 +952,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
609
952
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
610
953
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
611
954
|
},
|
|
955
|
+
{
|
|
956
|
+
title: "List Subscribers",
|
|
957
|
+
readOnlyHint: true,
|
|
958
|
+
destructiveHint: false,
|
|
959
|
+
idempotentHint: true,
|
|
960
|
+
openWorldHint: true
|
|
961
|
+
},
|
|
612
962
|
async (args) => {
|
|
613
963
|
const params = {};
|
|
614
964
|
if (args.page) params.page = args.page;
|
|
@@ -627,6 +977,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
627
977
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
628
978
|
last_name: z5.string().optional().describe("Subscriber's last name")
|
|
629
979
|
},
|
|
980
|
+
{
|
|
981
|
+
title: "Add Subscriber",
|
|
982
|
+
readOnlyHint: false,
|
|
983
|
+
destructiveHint: false,
|
|
984
|
+
idempotentHint: false,
|
|
985
|
+
openWorldHint: true
|
|
986
|
+
},
|
|
630
987
|
async (args) => {
|
|
631
988
|
const result = await client2.addSubscriber({
|
|
632
989
|
email: args.email,
|
|
@@ -646,6 +1003,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
646
1003
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
647
1004
|
preview_text: z5.string().optional().describe("Preview text shown in email clients")
|
|
648
1005
|
},
|
|
1006
|
+
{
|
|
1007
|
+
title: "Create Newsletter Draft",
|
|
1008
|
+
readOnlyHint: false,
|
|
1009
|
+
destructiveHint: false,
|
|
1010
|
+
idempotentHint: false,
|
|
1011
|
+
openWorldHint: true
|
|
1012
|
+
},
|
|
649
1013
|
async (args) => {
|
|
650
1014
|
const result = await client2.createBroadcast({
|
|
651
1015
|
subject: args.subject,
|
|
@@ -666,6 +1030,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
666
1030
|
content: z5.string().optional().describe("Updated HTML content"),
|
|
667
1031
|
preview_text: z5.string().optional().describe("Updated preview text")
|
|
668
1032
|
},
|
|
1033
|
+
{
|
|
1034
|
+
title: "Update Newsletter Draft",
|
|
1035
|
+
readOnlyHint: false,
|
|
1036
|
+
destructiveHint: false,
|
|
1037
|
+
idempotentHint: true,
|
|
1038
|
+
openWorldHint: true
|
|
1039
|
+
},
|
|
669
1040
|
async (args) => {
|
|
670
1041
|
const data = {};
|
|
671
1042
|
if (args.subject) data.subject = args.subject;
|
|
@@ -679,11 +1050,31 @@ function registerNewsletterTools(server2, client2) {
|
|
|
679
1050
|
);
|
|
680
1051
|
server2.tool(
|
|
681
1052
|
"send_newsletter",
|
|
682
|
-
"Send a newsletter/broadcast to subscribers. This action cannot be undone.",
|
|
1053
|
+
"Send a newsletter/broadcast to subscribers. This action cannot be undone. Requires confirmation before sending.",
|
|
1054
|
+
{
|
|
1055
|
+
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1056
|
+
confirmed: z5.boolean().optional().describe(
|
|
1057
|
+
"Set to true to confirm and send. If false or missing, returns a confirmation prompt."
|
|
1058
|
+
)
|
|
1059
|
+
},
|
|
683
1060
|
{
|
|
684
|
-
|
|
1061
|
+
title: "Send Newsletter",
|
|
1062
|
+
readOnlyHint: false,
|
|
1063
|
+
destructiveHint: false,
|
|
1064
|
+
idempotentHint: false,
|
|
1065
|
+
openWorldHint: true
|
|
685
1066
|
},
|
|
686
1067
|
async (args) => {
|
|
1068
|
+
if (!args.confirmed) {
|
|
1069
|
+
const preview = `## Send Newsletter Confirmation
|
|
1070
|
+
|
|
1071
|
+
**Broadcast ID:** ${args.broadcast_id}
|
|
1072
|
+
|
|
1073
|
+
This will send the newsletter to your subscriber list. **This action cannot be undone.**
|
|
1074
|
+
|
|
1075
|
+
Call this tool again with confirmed=true to send.`;
|
|
1076
|
+
return { content: [{ type: "text", text: preview }] };
|
|
1077
|
+
}
|
|
687
1078
|
await client2.sendBroadcast(args.broadcast_id);
|
|
688
1079
|
return {
|
|
689
1080
|
content: [{ type: "text", text: "Newsletter sent successfully." }]
|
|
@@ -696,6 +1087,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
696
1087
|
{
|
|
697
1088
|
page: z5.string().optional().describe("Page number for pagination")
|
|
698
1089
|
},
|
|
1090
|
+
{
|
|
1091
|
+
title: "List Newsletters",
|
|
1092
|
+
readOnlyHint: true,
|
|
1093
|
+
destructiveHint: false,
|
|
1094
|
+
idempotentHint: true,
|
|
1095
|
+
openWorldHint: true
|
|
1096
|
+
},
|
|
699
1097
|
async (args) => {
|
|
700
1098
|
const params = {};
|
|
701
1099
|
if (args.page) params.page = args.page;
|
|
@@ -709,6 +1107,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
709
1107
|
"list_tags",
|
|
710
1108
|
"List all subscriber tags from your email service provider.",
|
|
711
1109
|
{},
|
|
1110
|
+
{
|
|
1111
|
+
title: "List Subscriber Tags",
|
|
1112
|
+
readOnlyHint: true,
|
|
1113
|
+
destructiveHint: false,
|
|
1114
|
+
idempotentHint: true,
|
|
1115
|
+
openWorldHint: true
|
|
1116
|
+
},
|
|
712
1117
|
async () => {
|
|
713
1118
|
const result = await client2.listTags();
|
|
714
1119
|
return {
|
|
@@ -720,6 +1125,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
720
1125
|
"list_sequences",
|
|
721
1126
|
"List all email sequences/automations from your email service provider.",
|
|
722
1127
|
{},
|
|
1128
|
+
{
|
|
1129
|
+
title: "List Email Sequences",
|
|
1130
|
+
readOnlyHint: true,
|
|
1131
|
+
destructiveHint: false,
|
|
1132
|
+
idempotentHint: true,
|
|
1133
|
+
openWorldHint: true
|
|
1134
|
+
},
|
|
723
1135
|
async () => {
|
|
724
1136
|
const result = await client2.listSequences();
|
|
725
1137
|
return {
|
|
@@ -731,6 +1143,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
731
1143
|
"list_forms",
|
|
732
1144
|
"List all signup forms from your email service provider.",
|
|
733
1145
|
{},
|
|
1146
|
+
{
|
|
1147
|
+
title: "List Signup Forms",
|
|
1148
|
+
readOnlyHint: true,
|
|
1149
|
+
destructiveHint: false,
|
|
1150
|
+
idempotentHint: true,
|
|
1151
|
+
openWorldHint: true
|
|
1152
|
+
},
|
|
734
1153
|
async () => {
|
|
735
1154
|
const result = await client2.listForms();
|
|
736
1155
|
return {
|
|
@@ -750,6 +1169,13 @@ function registerRssTools(server2, client2) {
|
|
|
750
1169
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
751
1170
|
limit: z6.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
752
1171
|
},
|
|
1172
|
+
{
|
|
1173
|
+
title: "Fetch RSS Feed",
|
|
1174
|
+
readOnlyHint: true,
|
|
1175
|
+
destructiveHint: false,
|
|
1176
|
+
idempotentHint: true,
|
|
1177
|
+
openWorldHint: true
|
|
1178
|
+
},
|
|
753
1179
|
async (args) => {
|
|
754
1180
|
const result = await client2.fetchFeed(args.url, args.limit);
|
|
755
1181
|
return {
|
|
@@ -763,6 +1189,13 @@ function registerRssTools(server2, client2) {
|
|
|
763
1189
|
{
|
|
764
1190
|
url: z6.string().describe("The article URL to extract content from")
|
|
765
1191
|
},
|
|
1192
|
+
{
|
|
1193
|
+
title: "Fetch Article Content",
|
|
1194
|
+
readOnlyHint: true,
|
|
1195
|
+
destructiveHint: false,
|
|
1196
|
+
idempotentHint: true,
|
|
1197
|
+
openWorldHint: true
|
|
1198
|
+
},
|
|
766
1199
|
async (args) => {
|
|
767
1200
|
const result = await client2.fetchArticle(args.url);
|
|
768
1201
|
return {
|
|
@@ -778,6 +1211,13 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
778
1211
|
"get_account",
|
|
779
1212
|
"Get your BuzzPoster account details including name, email, subscription status, ESP configuration, and connected platforms.",
|
|
780
1213
|
{},
|
|
1214
|
+
{
|
|
1215
|
+
title: "Get Account Details",
|
|
1216
|
+
readOnlyHint: true,
|
|
1217
|
+
destructiveHint: false,
|
|
1218
|
+
idempotentHint: true,
|
|
1219
|
+
openWorldHint: false
|
|
1220
|
+
},
|
|
781
1221
|
async () => {
|
|
782
1222
|
const result = await client2.getAccount();
|
|
783
1223
|
return {
|
|
@@ -793,6 +1233,13 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
793
1233
|
"get_brand_voice",
|
|
794
1234
|
"Get the customer's brand voice profile and writing rules. IMPORTANT: Call this tool BEFORE creating any post, draft, or content. Use the returned voice description, rules, and examples to match the customer's tone and style in everything you write for them.",
|
|
795
1235
|
{},
|
|
1236
|
+
{
|
|
1237
|
+
title: "Get Brand Voice",
|
|
1238
|
+
readOnlyHint: true,
|
|
1239
|
+
destructiveHint: false,
|
|
1240
|
+
idempotentHint: true,
|
|
1241
|
+
openWorldHint: false
|
|
1242
|
+
},
|
|
796
1243
|
async () => {
|
|
797
1244
|
try {
|
|
798
1245
|
const voice = await client2.getBrandVoice();
|
|
@@ -860,6 +1307,13 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
860
1307
|
"get_knowledge_base",
|
|
861
1308
|
"Get all items from the customer's knowledge base. Use this to access reference material about the business, products, team, competitors, and other context that helps you write better content.",
|
|
862
1309
|
{},
|
|
1310
|
+
{
|
|
1311
|
+
title: "Get Knowledge Base",
|
|
1312
|
+
readOnlyHint: true,
|
|
1313
|
+
destructiveHint: false,
|
|
1314
|
+
idempotentHint: true,
|
|
1315
|
+
openWorldHint: false
|
|
1316
|
+
},
|
|
863
1317
|
async () => {
|
|
864
1318
|
try {
|
|
865
1319
|
const data = await client2.listKnowledge();
|
|
@@ -911,6 +1365,13 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
911
1365
|
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
912
1366
|
)
|
|
913
1367
|
},
|
|
1368
|
+
{
|
|
1369
|
+
title: "Search Knowledge Base",
|
|
1370
|
+
readOnlyHint: true,
|
|
1371
|
+
destructiveHint: false,
|
|
1372
|
+
idempotentHint: true,
|
|
1373
|
+
openWorldHint: false
|
|
1374
|
+
},
|
|
914
1375
|
async ({ query }) => {
|
|
915
1376
|
try {
|
|
916
1377
|
const data = await client2.listKnowledge({ tag: query });
|
|
@@ -955,6 +1416,13 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
955
1416
|
content: z7.string().describe("The content/text to save"),
|
|
956
1417
|
tags: z7.array(z7.string()).optional().describe("Optional tags for categorization")
|
|
957
1418
|
},
|
|
1419
|
+
{
|
|
1420
|
+
title: "Add Knowledge Item",
|
|
1421
|
+
readOnlyHint: false,
|
|
1422
|
+
destructiveHint: false,
|
|
1423
|
+
idempotentHint: false,
|
|
1424
|
+
openWorldHint: false
|
|
1425
|
+
},
|
|
958
1426
|
async ({ title, content, tags }) => {
|
|
959
1427
|
try {
|
|
960
1428
|
await client2.createKnowledge({
|
|
@@ -979,6 +1447,674 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
979
1447
|
);
|
|
980
1448
|
}
|
|
981
1449
|
|
|
1450
|
+
// src/tools/audience.ts
|
|
1451
|
+
import { z as z8 } from "zod";
|
|
1452
|
+
function registerAudienceTools(server2, client2) {
|
|
1453
|
+
server2.tool(
|
|
1454
|
+
"get_audience",
|
|
1455
|
+
"Get the target audience profile for content creation. Use this tool to understand WHO the content is for \u2014 their demographics, pain points, motivations, preferred platforms, and tone preferences. Call this BEFORE writing any post or content so you can tailor messaging to resonate with the intended audience.",
|
|
1456
|
+
{
|
|
1457
|
+
audienceId: z8.string().optional().describe("Specific audience profile ID. If omitted, returns the default audience.")
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
title: "Get Audience Profile",
|
|
1461
|
+
readOnlyHint: true,
|
|
1462
|
+
destructiveHint: false,
|
|
1463
|
+
idempotentHint: true,
|
|
1464
|
+
openWorldHint: false
|
|
1465
|
+
},
|
|
1466
|
+
async ({ audienceId }) => {
|
|
1467
|
+
try {
|
|
1468
|
+
const audience = audienceId ? await client2.getAudience(audienceId) : await client2.getDefaultAudience();
|
|
1469
|
+
const lines = [];
|
|
1470
|
+
lines.push(`## Target Audience: ${audience.name}`);
|
|
1471
|
+
lines.push("");
|
|
1472
|
+
if (audience.description) {
|
|
1473
|
+
lines.push("### Description");
|
|
1474
|
+
lines.push(audience.description);
|
|
1475
|
+
lines.push("");
|
|
1476
|
+
}
|
|
1477
|
+
if (audience.demographics) {
|
|
1478
|
+
lines.push("### Demographics");
|
|
1479
|
+
lines.push(audience.demographics);
|
|
1480
|
+
lines.push("");
|
|
1481
|
+
}
|
|
1482
|
+
if (audience.painPoints && audience.painPoints.length > 0) {
|
|
1483
|
+
lines.push("### Pain Points");
|
|
1484
|
+
for (const point of audience.painPoints) {
|
|
1485
|
+
lines.push(`- ${point}`);
|
|
1486
|
+
}
|
|
1487
|
+
lines.push("");
|
|
1488
|
+
}
|
|
1489
|
+
if (audience.motivations && audience.motivations.length > 0) {
|
|
1490
|
+
lines.push("### Motivations");
|
|
1491
|
+
for (const motivation of audience.motivations) {
|
|
1492
|
+
lines.push(`- ${motivation}`);
|
|
1493
|
+
}
|
|
1494
|
+
lines.push("");
|
|
1495
|
+
}
|
|
1496
|
+
if (audience.platforms && audience.platforms.length > 0) {
|
|
1497
|
+
lines.push("### Active Platforms");
|
|
1498
|
+
lines.push(audience.platforms.join(", "));
|
|
1499
|
+
lines.push("");
|
|
1500
|
+
}
|
|
1501
|
+
if (audience.toneNotes) {
|
|
1502
|
+
lines.push("### Tone Notes");
|
|
1503
|
+
lines.push(audience.toneNotes);
|
|
1504
|
+
lines.push("");
|
|
1505
|
+
}
|
|
1506
|
+
if (audience.contentPreferences) {
|
|
1507
|
+
lines.push("### Content Preferences");
|
|
1508
|
+
lines.push(audience.contentPreferences);
|
|
1509
|
+
lines.push("");
|
|
1510
|
+
}
|
|
1511
|
+
return {
|
|
1512
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1513
|
+
};
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1516
|
+
if (message.includes("404") || message.includes("No audience")) {
|
|
1517
|
+
return {
|
|
1518
|
+
content: [
|
|
1519
|
+
{
|
|
1520
|
+
type: "text",
|
|
1521
|
+
text: "No audience profile has been configured yet. The customer can set one up at their BuzzPoster dashboard under Audiences."
|
|
1522
|
+
}
|
|
1523
|
+
]
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
throw error;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/tools/newsletter-template.ts
|
|
1533
|
+
import { z as z9 } from "zod";
|
|
1534
|
+
function registerNewsletterTemplateTools(server2, client2) {
|
|
1535
|
+
server2.tool(
|
|
1536
|
+
"get_newsletter_template",
|
|
1537
|
+
"Get the customer's newsletter template -- the blueprint for how their newsletter is structured. IMPORTANT: Call this BEFORE writing any newsletter. The template defines the exact section order, what each section should contain, word counts, content sources, and styling. Follow the template structure precisely when generating newsletter content. If no template_id is specified, returns the default template.",
|
|
1538
|
+
{
|
|
1539
|
+
templateId: z9.string().optional().describe(
|
|
1540
|
+
"Specific template ID. If omitted, returns the default template."
|
|
1541
|
+
)
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
title: "Get Newsletter Template",
|
|
1545
|
+
readOnlyHint: true,
|
|
1546
|
+
destructiveHint: false,
|
|
1547
|
+
idempotentHint: true,
|
|
1548
|
+
openWorldHint: false
|
|
1549
|
+
},
|
|
1550
|
+
async ({ templateId }) => {
|
|
1551
|
+
try {
|
|
1552
|
+
const template = await client2.getTemplate(templateId);
|
|
1553
|
+
const lines = [];
|
|
1554
|
+
lines.push(`## Newsletter Template: ${template.name}`);
|
|
1555
|
+
if (template.description) lines.push(template.description);
|
|
1556
|
+
lines.push("");
|
|
1557
|
+
if (template.audienceId)
|
|
1558
|
+
lines.push(`Audience ID: ${template.audienceId}`);
|
|
1559
|
+
if (template.sendCadence)
|
|
1560
|
+
lines.push(`Cadence: ${template.sendCadence}`);
|
|
1561
|
+
if (template.subjectPattern)
|
|
1562
|
+
lines.push(`Subject pattern: ${template.subjectPattern}`);
|
|
1563
|
+
if (template.previewTextPattern)
|
|
1564
|
+
lines.push(`Preview text pattern: ${template.previewTextPattern}`);
|
|
1565
|
+
lines.push("");
|
|
1566
|
+
if (template.sections && template.sections.length > 0) {
|
|
1567
|
+
lines.push("### Sections (in order):");
|
|
1568
|
+
lines.push("");
|
|
1569
|
+
for (let i = 0; i < template.sections.length; i++) {
|
|
1570
|
+
const s = template.sections[i];
|
|
1571
|
+
lines.push(`**${i + 1}. ${s.label}** (${s.type})`);
|
|
1572
|
+
if (s.instructions)
|
|
1573
|
+
lines.push(`Instructions: ${s.instructions}`);
|
|
1574
|
+
if (s.word_count)
|
|
1575
|
+
lines.push(
|
|
1576
|
+
`Word count: ${s.word_count.min}-${s.word_count.max} words`
|
|
1577
|
+
);
|
|
1578
|
+
if (s.tone_override) lines.push(`Tone: ${s.tone_override}`);
|
|
1579
|
+
if (s.content_source) {
|
|
1580
|
+
const src = s.content_source;
|
|
1581
|
+
if (src.type === "rss" && src.feed_urls?.length)
|
|
1582
|
+
lines.push(`Content source: RSS - ${src.feed_urls.join(", ")}`);
|
|
1583
|
+
else if (src.type === "knowledge" && src.knowledge_item_ids?.length)
|
|
1584
|
+
lines.push(
|
|
1585
|
+
`Content source: Knowledge items ${src.knowledge_item_ids.join(", ")}`
|
|
1586
|
+
);
|
|
1587
|
+
else lines.push(`Content source: ${src.type}`);
|
|
1588
|
+
}
|
|
1589
|
+
if (s.count) lines.push(`Count: ${s.count} items`);
|
|
1590
|
+
lines.push(
|
|
1591
|
+
`Required: ${s.required !== false ? "yes" : "no"}`
|
|
1592
|
+
);
|
|
1593
|
+
if (s.type === "cta") {
|
|
1594
|
+
if (s.button_text) lines.push(`Button text: ${s.button_text}`);
|
|
1595
|
+
if (s.button_url) lines.push(`Button URL: ${s.button_url}`);
|
|
1596
|
+
}
|
|
1597
|
+
if (s.type === "sponsor") {
|
|
1598
|
+
if (s.sponsor_name)
|
|
1599
|
+
lines.push(`Sponsor: ${s.sponsor_name}`);
|
|
1600
|
+
if (s.talking_points)
|
|
1601
|
+
lines.push(`Talking points: ${s.talking_points}`);
|
|
1602
|
+
}
|
|
1603
|
+
if (s.type === "header") {
|
|
1604
|
+
if (s.tagline) lines.push(`Tagline: ${s.tagline}`);
|
|
1605
|
+
}
|
|
1606
|
+
if (s.type === "signoff") {
|
|
1607
|
+
if (s.author_name)
|
|
1608
|
+
lines.push(`Author: ${s.author_name}`);
|
|
1609
|
+
if (s.author_title)
|
|
1610
|
+
lines.push(`Title: ${s.author_title}`);
|
|
1611
|
+
}
|
|
1612
|
+
lines.push("");
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
if (template.style) {
|
|
1616
|
+
lines.push("### Style Guide:");
|
|
1617
|
+
const st = template.style;
|
|
1618
|
+
if (st.primary_color) lines.push(`Primary color: ${st.primary_color}`);
|
|
1619
|
+
if (st.accent_color) lines.push(`Accent color: ${st.accent_color}`);
|
|
1620
|
+
if (st.font_family) lines.push(`Font: ${st.font_family}`);
|
|
1621
|
+
if (st.heading_font) lines.push(`Heading font: ${st.heading_font}`);
|
|
1622
|
+
if (st.content_width)
|
|
1623
|
+
lines.push(`Content width: ${st.content_width}px`);
|
|
1624
|
+
if (st.text_color) lines.push(`Text color: ${st.text_color}`);
|
|
1625
|
+
if (st.link_color) lines.push(`Link color: ${st.link_color}`);
|
|
1626
|
+
if (st.background_color)
|
|
1627
|
+
lines.push(`Background: ${st.background_color}`);
|
|
1628
|
+
if (st.button_style)
|
|
1629
|
+
lines.push(
|
|
1630
|
+
`Button style: ${st.button_style.color} text on ${st.button_style.text_color}, ${st.button_style.shape}`
|
|
1631
|
+
);
|
|
1632
|
+
lines.push("");
|
|
1633
|
+
lines.push(
|
|
1634
|
+
"NOTE: Apply these style values as inline CSS when generating the newsletter HTML."
|
|
1635
|
+
);
|
|
1636
|
+
lines.push("");
|
|
1637
|
+
}
|
|
1638
|
+
if (template.rssFeedUrls && template.rssFeedUrls.length > 0) {
|
|
1639
|
+
lines.push("### Linked RSS Feeds:");
|
|
1640
|
+
for (const url of template.rssFeedUrls) {
|
|
1641
|
+
lines.push(`- ${url}`);
|
|
1642
|
+
}
|
|
1643
|
+
lines.push("");
|
|
1644
|
+
}
|
|
1645
|
+
if (template.knowledgeItemIds && template.knowledgeItemIds.length > 0) {
|
|
1646
|
+
lines.push("### Reference Material:");
|
|
1647
|
+
lines.push(
|
|
1648
|
+
`Knowledge item IDs: ${template.knowledgeItemIds.join(", ")}`
|
|
1649
|
+
);
|
|
1650
|
+
lines.push("");
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1654
|
+
};
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1657
|
+
if (message.includes("404") || message.includes("No newsletter templates")) {
|
|
1658
|
+
return {
|
|
1659
|
+
content: [
|
|
1660
|
+
{
|
|
1661
|
+
type: "text",
|
|
1662
|
+
text: "No newsletter template has been configured yet. The customer can set one up at their BuzzPoster dashboard under Templates."
|
|
1663
|
+
}
|
|
1664
|
+
]
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
throw error;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
);
|
|
1671
|
+
server2.tool(
|
|
1672
|
+
"get_past_newsletters",
|
|
1673
|
+
"Retrieve the customer's past newsletters for reference. Use this to maintain continuity, match previous formatting, avoid repeating topics, and reference previous editions. Call this when writing a new newsletter to see what was covered recently.",
|
|
1674
|
+
{
|
|
1675
|
+
limit: z9.number().optional().describe("Number of past newsletters to retrieve. Default 5, max 50."),
|
|
1676
|
+
templateId: z9.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
title: "Get Past Newsletters",
|
|
1680
|
+
readOnlyHint: true,
|
|
1681
|
+
destructiveHint: false,
|
|
1682
|
+
idempotentHint: true,
|
|
1683
|
+
openWorldHint: false
|
|
1684
|
+
},
|
|
1685
|
+
async ({ limit, templateId }) => {
|
|
1686
|
+
try {
|
|
1687
|
+
const params = {};
|
|
1688
|
+
if (limit) params.limit = String(Math.min(limit, 50));
|
|
1689
|
+
else params.limit = "5";
|
|
1690
|
+
if (templateId) params.template_id = templateId;
|
|
1691
|
+
const newsletters = await client2.listNewsletterArchive(
|
|
1692
|
+
params
|
|
1693
|
+
);
|
|
1694
|
+
if (!newsletters || newsletters.length === 0) {
|
|
1695
|
+
return {
|
|
1696
|
+
content: [
|
|
1697
|
+
{
|
|
1698
|
+
type: "text",
|
|
1699
|
+
text: "No past newsletters found. This will be the first edition!"
|
|
1700
|
+
}
|
|
1701
|
+
]
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
const lines = [];
|
|
1705
|
+
lines.push(
|
|
1706
|
+
`## Past Newsletters (${newsletters.length} most recent)`
|
|
1707
|
+
);
|
|
1708
|
+
lines.push("");
|
|
1709
|
+
for (const nl of newsletters) {
|
|
1710
|
+
lines.push(`### ${nl.subject} -- ${nl.sentAt || nl.createdAt}`);
|
|
1711
|
+
if (nl.notes) lines.push(`Notes: ${nl.notes}`);
|
|
1712
|
+
if (nl.metrics) {
|
|
1713
|
+
const m = nl.metrics;
|
|
1714
|
+
const parts = [];
|
|
1715
|
+
if (m.open_rate) parts.push(`Open rate: ${m.open_rate}`);
|
|
1716
|
+
if (m.click_rate) parts.push(`Click rate: ${m.click_rate}`);
|
|
1717
|
+
if (parts.length) lines.push(`Metrics: ${parts.join(", ")}`);
|
|
1718
|
+
}
|
|
1719
|
+
lines.push("---");
|
|
1720
|
+
const textContent = nl.contentHtml ? nl.contentHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 500) : "(no content)";
|
|
1721
|
+
lines.push(textContent);
|
|
1722
|
+
lines.push(
|
|
1723
|
+
`[Full content available via get_past_newsletter tool with ID: ${nl.id}]`
|
|
1724
|
+
);
|
|
1725
|
+
lines.push("");
|
|
1726
|
+
}
|
|
1727
|
+
return {
|
|
1728
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1729
|
+
};
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1732
|
+
throw new Error(`Failed to fetch past newsletters: ${message}`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
);
|
|
1736
|
+
server2.tool(
|
|
1737
|
+
"get_past_newsletter",
|
|
1738
|
+
"Get the full content of a specific past newsletter by ID. Use when you need to reference the complete text of a previous edition, check exact formatting, or continue a series.",
|
|
1739
|
+
{
|
|
1740
|
+
newsletterId: z9.string().describe("The ID of the archived newsletter to retrieve.")
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
title: "Get Past Newsletter Detail",
|
|
1744
|
+
readOnlyHint: true,
|
|
1745
|
+
destructiveHint: false,
|
|
1746
|
+
idempotentHint: true,
|
|
1747
|
+
openWorldHint: false
|
|
1748
|
+
},
|
|
1749
|
+
async ({ newsletterId }) => {
|
|
1750
|
+
try {
|
|
1751
|
+
const newsletter = await client2.getArchivedNewsletter(
|
|
1752
|
+
newsletterId
|
|
1753
|
+
);
|
|
1754
|
+
const lines = [];
|
|
1755
|
+
lines.push(`## ${newsletter.subject}`);
|
|
1756
|
+
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
1757
|
+
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
1758
|
+
lines.push("");
|
|
1759
|
+
lines.push("### Full HTML Content:");
|
|
1760
|
+
lines.push(newsletter.contentHtml);
|
|
1761
|
+
return {
|
|
1762
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1763
|
+
};
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1766
|
+
if (message.includes("404")) {
|
|
1767
|
+
return {
|
|
1768
|
+
content: [
|
|
1769
|
+
{
|
|
1770
|
+
type: "text",
|
|
1771
|
+
text: "Newsletter not found. Check the ID and try again."
|
|
1772
|
+
}
|
|
1773
|
+
]
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
throw error;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
);
|
|
1780
|
+
server2.tool(
|
|
1781
|
+
"save_newsletter",
|
|
1782
|
+
"Save a generated newsletter to the archive. Call this after the customer approves a newsletter you've written, so it's stored for future reference. Include the subject line and full HTML content.",
|
|
1783
|
+
{
|
|
1784
|
+
subject: z9.string().describe("The newsletter subject line."),
|
|
1785
|
+
contentHtml: z9.string().describe("The full HTML content of the newsletter."),
|
|
1786
|
+
templateId: z9.string().optional().describe("The template ID used to generate this newsletter."),
|
|
1787
|
+
notes: z9.string().optional().describe(
|
|
1788
|
+
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
1789
|
+
)
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
title: "Save Newsletter to Archive",
|
|
1793
|
+
readOnlyHint: false,
|
|
1794
|
+
destructiveHint: false,
|
|
1795
|
+
idempotentHint: false,
|
|
1796
|
+
openWorldHint: false
|
|
1797
|
+
},
|
|
1798
|
+
async ({ subject, contentHtml, templateId, notes }) => {
|
|
1799
|
+
try {
|
|
1800
|
+
const data = {
|
|
1801
|
+
subject,
|
|
1802
|
+
contentHtml
|
|
1803
|
+
};
|
|
1804
|
+
if (templateId) data.templateId = Number(templateId);
|
|
1805
|
+
if (notes) data.notes = notes;
|
|
1806
|
+
await client2.saveNewsletterToArchive(data);
|
|
1807
|
+
return {
|
|
1808
|
+
content: [
|
|
1809
|
+
{
|
|
1810
|
+
type: "text",
|
|
1811
|
+
text: `Newsletter "${subject}" has been saved to the archive successfully.`
|
|
1812
|
+
}
|
|
1813
|
+
]
|
|
1814
|
+
};
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1817
|
+
throw new Error(`Failed to save newsletter: ${message}`);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// src/tools/calendar.ts
|
|
1824
|
+
import { z as z10 } from "zod";
|
|
1825
|
+
function registerCalendarTools(server2, client2) {
|
|
1826
|
+
server2.tool(
|
|
1827
|
+
"get_calendar",
|
|
1828
|
+
"View the customer's content calendar -- all scheduled, published, and draft posts across social media and newsletters. Use this to check what's already scheduled before creating new content, avoid posting conflicts, and maintain a balanced content mix.",
|
|
1829
|
+
{
|
|
1830
|
+
from: z10.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
1831
|
+
to: z10.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
1832
|
+
status: z10.string().optional().describe("Filter by status: scheduled, published, draft, failed"),
|
|
1833
|
+
type: z10.string().optional().describe("Filter by type: social_post or newsletter")
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
title: "Get Content Calendar",
|
|
1837
|
+
readOnlyHint: true,
|
|
1838
|
+
destructiveHint: false,
|
|
1839
|
+
idempotentHint: true,
|
|
1840
|
+
openWorldHint: false
|
|
1841
|
+
},
|
|
1842
|
+
async (args) => {
|
|
1843
|
+
const params = {};
|
|
1844
|
+
if (args.from) params.from = args.from;
|
|
1845
|
+
if (args.to) params.to = args.to;
|
|
1846
|
+
if (args.status) params.status = args.status;
|
|
1847
|
+
if (args.type) params.type = args.type;
|
|
1848
|
+
const data = await client2.getCalendar(params);
|
|
1849
|
+
const items = data?.items ?? [];
|
|
1850
|
+
const scheduled = items.filter((i) => i.status === "scheduled");
|
|
1851
|
+
const published = items.filter((i) => i.status === "published");
|
|
1852
|
+
let text = "## Content Calendar\n\n";
|
|
1853
|
+
if (scheduled.length > 0) {
|
|
1854
|
+
text += "### Scheduled\n";
|
|
1855
|
+
for (const item of scheduled) {
|
|
1856
|
+
const date = item.scheduled_for ? new Date(item.scheduled_for).toLocaleString() : "TBD";
|
|
1857
|
+
if (item.type === "newsletter") {
|
|
1858
|
+
text += `\u{1F4E7} ${date} -- Newsletter: "${item.content_preview}"
|
|
1859
|
+
`;
|
|
1860
|
+
} else {
|
|
1861
|
+
const platforms = (item.platforms ?? []).join(", ");
|
|
1862
|
+
text += `\u{1F4C5} ${date} -- ${platforms} -- "${item.content_preview}"
|
|
1863
|
+
`;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
text += "\n";
|
|
1867
|
+
} else {
|
|
1868
|
+
text += "### Scheduled\nNo scheduled content.\n\n";
|
|
1869
|
+
}
|
|
1870
|
+
if (published.length > 0) {
|
|
1871
|
+
text += "### Recently Published\n";
|
|
1872
|
+
for (const item of published.slice(0, 10)) {
|
|
1873
|
+
const date = item.published_at ? new Date(item.published_at).toLocaleString() : "";
|
|
1874
|
+
if (item.type === "newsletter") {
|
|
1875
|
+
text += `\u2705 ${date} -- Newsletter: "${item.content_preview}"
|
|
1876
|
+
`;
|
|
1877
|
+
} else {
|
|
1878
|
+
const platforms = (item.platforms ?? []).join(", ");
|
|
1879
|
+
text += `\u2705 ${date} -- ${platforms} -- "${item.content_preview}"
|
|
1880
|
+
`;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
text += "\n";
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
const nextSlot = await client2.getNextSlot();
|
|
1887
|
+
if (nextSlot?.scheduledFor) {
|
|
1888
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
1889
|
+
const dayNames = [
|
|
1890
|
+
"Sunday",
|
|
1891
|
+
"Monday",
|
|
1892
|
+
"Tuesday",
|
|
1893
|
+
"Wednesday",
|
|
1894
|
+
"Thursday",
|
|
1895
|
+
"Friday",
|
|
1896
|
+
"Saturday"
|
|
1897
|
+
];
|
|
1898
|
+
text += `### Queue: Next slot
|
|
1899
|
+
\u23ED\uFE0F ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
1900
|
+
`;
|
|
1901
|
+
}
|
|
1902
|
+
} catch {
|
|
1903
|
+
}
|
|
1904
|
+
return {
|
|
1905
|
+
content: [{ type: "text", text }]
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
);
|
|
1909
|
+
server2.tool(
|
|
1910
|
+
"get_queue",
|
|
1911
|
+
"View the customer's posting queue schedule -- their recurring weekly time slots for automatic post scheduling. Use this to understand when posts go out and to schedule content into the next available slot.",
|
|
1912
|
+
{},
|
|
1913
|
+
{
|
|
1914
|
+
title: "Get Posting Queue",
|
|
1915
|
+
readOnlyHint: true,
|
|
1916
|
+
destructiveHint: false,
|
|
1917
|
+
idempotentHint: true,
|
|
1918
|
+
openWorldHint: false
|
|
1919
|
+
},
|
|
1920
|
+
async () => {
|
|
1921
|
+
const data = await client2.getQueue();
|
|
1922
|
+
const dayNames = [
|
|
1923
|
+
"Sunday",
|
|
1924
|
+
"Monday",
|
|
1925
|
+
"Tuesday",
|
|
1926
|
+
"Wednesday",
|
|
1927
|
+
"Thursday",
|
|
1928
|
+
"Friday",
|
|
1929
|
+
"Saturday"
|
|
1930
|
+
];
|
|
1931
|
+
let text = "## Posting Queue\n\n";
|
|
1932
|
+
text += `Timezone: ${data?.timezone ?? "UTC"}
|
|
1933
|
+
`;
|
|
1934
|
+
text += `Active: ${data?.active ? "Yes" : "No"}
|
|
1935
|
+
|
|
1936
|
+
`;
|
|
1937
|
+
const slots = data?.slots ?? [];
|
|
1938
|
+
if (slots.length === 0) {
|
|
1939
|
+
text += "No queue slots configured.\n";
|
|
1940
|
+
} else {
|
|
1941
|
+
text += "### Weekly Schedule\n";
|
|
1942
|
+
for (const slot of slots) {
|
|
1943
|
+
const day = dayNames[slot.dayOfWeek] ?? `Day ${slot.dayOfWeek}`;
|
|
1944
|
+
const platforms = (slot.platforms ?? []).map((p) => `[${p}]`).join(" ");
|
|
1945
|
+
text += `${day}: ${slot.time} ${platforms}
|
|
1946
|
+
`;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
try {
|
|
1950
|
+
const nextSlot = await client2.getNextSlot();
|
|
1951
|
+
if (nextSlot?.scheduledFor) {
|
|
1952
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
1953
|
+
text += `
|
|
1954
|
+
Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
1955
|
+
`;
|
|
1956
|
+
}
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
content: [{ type: "text", text }]
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
);
|
|
1964
|
+
server2.tool(
|
|
1965
|
+
"schedule_to_queue",
|
|
1966
|
+
"Add a post to the customer's content queue. The post will be automatically scheduled to the next available time slot. Requires confirmation before scheduling.",
|
|
1967
|
+
{
|
|
1968
|
+
content: z10.string().describe("The text content of the post"),
|
|
1969
|
+
platforms: z10.array(z10.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
1970
|
+
media_urls: z10.array(z10.string()).optional().describe("Public URLs of media to attach"),
|
|
1971
|
+
confirmed: z10.boolean().optional().describe(
|
|
1972
|
+
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
1973
|
+
)
|
|
1974
|
+
},
|
|
1975
|
+
{
|
|
1976
|
+
title: "Schedule Post to Queue",
|
|
1977
|
+
readOnlyHint: false,
|
|
1978
|
+
destructiveHint: false,
|
|
1979
|
+
idempotentHint: false,
|
|
1980
|
+
openWorldHint: true
|
|
1981
|
+
},
|
|
1982
|
+
async (args) => {
|
|
1983
|
+
const nextSlot = await client2.getNextSlot();
|
|
1984
|
+
if (!nextSlot?.scheduledFor) {
|
|
1985
|
+
return {
|
|
1986
|
+
content: [
|
|
1987
|
+
{
|
|
1988
|
+
type: "text",
|
|
1989
|
+
text: "No queue slots configured. Set up your posting queue first using the dashboard or ask to configure queue slots."
|
|
1990
|
+
}
|
|
1991
|
+
]
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
if (!args.confirmed) {
|
|
1995
|
+
const slotDate2 = new Date(nextSlot.scheduledFor);
|
|
1996
|
+
const preview = `## Queue Post Preview
|
|
1997
|
+
|
|
1998
|
+
**Content:** "${args.content}"
|
|
1999
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
2000
|
+
**Next queue slot:** ${slotDate2.toLocaleString()}
|
|
2001
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
2002
|
+
` : "") + `
|
|
2003
|
+
Call this tool again with confirmed=true to schedule.`;
|
|
2004
|
+
return { content: [{ type: "text", text: preview }] };
|
|
2005
|
+
}
|
|
2006
|
+
const body = {
|
|
2007
|
+
content: args.content,
|
|
2008
|
+
platforms: args.platforms.map((p) => ({ platform: p })),
|
|
2009
|
+
scheduledFor: nextSlot.scheduledFor,
|
|
2010
|
+
timezone: nextSlot.timezone ?? "UTC"
|
|
2011
|
+
};
|
|
2012
|
+
if (args.media_urls?.length) {
|
|
2013
|
+
body.mediaItems = args.media_urls.map((url) => ({
|
|
2014
|
+
type: url.match(/\.(mp4|mov|avi|webm)$/i) ? "video" : "image",
|
|
2015
|
+
url
|
|
2016
|
+
}));
|
|
2017
|
+
}
|
|
2018
|
+
const result = await client2.createPost(body);
|
|
2019
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
2020
|
+
return {
|
|
2021
|
+
content: [
|
|
2022
|
+
{
|
|
2023
|
+
type: "text",
|
|
2024
|
+
text: `Post scheduled to queue slot: ${slotDate.toLocaleString()}
|
|
2025
|
+
|
|
2026
|
+
${JSON.stringify(result, null, 2)}`
|
|
2027
|
+
}
|
|
2028
|
+
]
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// src/tools/notifications.ts
|
|
2035
|
+
import { z as z11 } from "zod";
|
|
2036
|
+
function timeAgo(dateStr) {
|
|
2037
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2038
|
+
const minutes = Math.floor(diff / 6e4);
|
|
2039
|
+
if (minutes < 1) return "just now";
|
|
2040
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
2041
|
+
const hours = Math.floor(minutes / 60);
|
|
2042
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2043
|
+
const days = Math.floor(hours / 24);
|
|
2044
|
+
return `${days}d ago`;
|
|
2045
|
+
}
|
|
2046
|
+
function registerNotificationTools(server2, client2) {
|
|
2047
|
+
server2.tool(
|
|
2048
|
+
"get_notifications",
|
|
2049
|
+
"Get recent notifications including post publishing results, account health warnings, and errors. Use this to check if any posts failed or if any accounts need attention.",
|
|
2050
|
+
{
|
|
2051
|
+
unread_only: z11.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
2052
|
+
limit: z11.number().optional().describe("Max notifications to return. Default 10.")
|
|
2053
|
+
},
|
|
2054
|
+
{
|
|
2055
|
+
title: "Get Notifications",
|
|
2056
|
+
readOnlyHint: true,
|
|
2057
|
+
destructiveHint: false,
|
|
2058
|
+
idempotentHint: true,
|
|
2059
|
+
openWorldHint: false
|
|
2060
|
+
},
|
|
2061
|
+
async (args) => {
|
|
2062
|
+
const params = {};
|
|
2063
|
+
if (args.unread_only) params.unread = "true";
|
|
2064
|
+
if (args.limit) params.limit = String(args.limit);
|
|
2065
|
+
else params.limit = "10";
|
|
2066
|
+
const result = await client2.getNotifications(params);
|
|
2067
|
+
const notifications = result.notifications ?? [];
|
|
2068
|
+
const unreadCount = result.unread_count ?? 0;
|
|
2069
|
+
if (notifications.length === 0) {
|
|
2070
|
+
return {
|
|
2071
|
+
content: [{
|
|
2072
|
+
type: "text",
|
|
2073
|
+
text: `## Notifications${args.unread_only ? " (unread)" : ""}
|
|
2074
|
+
|
|
2075
|
+
No notifications found. All clear!`
|
|
2076
|
+
}]
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
const severityIcon = {
|
|
2080
|
+
success: "\u{1F7E2}",
|
|
2081
|
+
warning: "\u{1F7E1}",
|
|
2082
|
+
error: "\u{1F534}",
|
|
2083
|
+
info: "\u{1F535}"
|
|
2084
|
+
};
|
|
2085
|
+
const lines = notifications.map((n) => {
|
|
2086
|
+
const icon = severityIcon[n.severity] ?? "\u2B1C";
|
|
2087
|
+
const time = n.createdAt ? timeAgo(n.createdAt) : "";
|
|
2088
|
+
let line = `${icon} **${time}** \u2014 ${n.title}
|
|
2089
|
+
"${n.message}"`;
|
|
2090
|
+
if (n.actionUrl || n.actionLabel) {
|
|
2091
|
+
const action = n.actionLabel || "View";
|
|
2092
|
+
if (n.resourceType === "post" && n.resourceId) {
|
|
2093
|
+
line += `
|
|
2094
|
+
Action: ${action} \u2192 retry_post with post_id "${n.resourceId}"`;
|
|
2095
|
+
} else {
|
|
2096
|
+
line += `
|
|
2097
|
+
Action: ${action} \u2192 ${n.actionUrl}`;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return line;
|
|
2101
|
+
});
|
|
2102
|
+
const header = `## Notifications (${unreadCount} unread)
|
|
2103
|
+
|
|
2104
|
+
`;
|
|
2105
|
+
const footer = `
|
|
2106
|
+
|
|
2107
|
+
Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" : ""} notifications.`;
|
|
2108
|
+
return {
|
|
2109
|
+
content: [{
|
|
2110
|
+
type: "text",
|
|
2111
|
+
text: header + lines.join("\n\n") + footer
|
|
2112
|
+
}]
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
982
2118
|
// src/index.ts
|
|
983
2119
|
var apiKey = process.env.BUZZPOSTER_API_KEY;
|
|
984
2120
|
var apiUrl = process.env.BUZZPOSTER_API_URL ?? "https://api.buzzposter.com";
|
|
@@ -1006,5 +2142,9 @@ registerRssTools(server, client);
|
|
|
1006
2142
|
registerAccountInfoTool(server, client);
|
|
1007
2143
|
registerBrandVoiceTools(server, client);
|
|
1008
2144
|
registerKnowledgeTools(server, client);
|
|
2145
|
+
registerAudienceTools(server, client);
|
|
2146
|
+
registerNewsletterTemplateTools(server, client);
|
|
2147
|
+
registerCalendarTools(server, client);
|
|
2148
|
+
registerNotificationTools(server, client);
|
|
1009
2149
|
var transport = new StdioServerTransport();
|
|
1010
2150
|
await server.connect(transport);
|