@buzzposter/mcp 0.1.2 → 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 +1364 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -152,6 +152,64 @@ var BuzzPosterClient = class {
|
|
|
152
152
|
async listForms() {
|
|
153
153
|
return this.request("GET", "/api/v1/newsletters/forms");
|
|
154
154
|
}
|
|
155
|
+
// Knowledge
|
|
156
|
+
async listKnowledge(params) {
|
|
157
|
+
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
158
|
+
}
|
|
159
|
+
async createKnowledge(data) {
|
|
160
|
+
return this.request("POST", "/api/v1/knowledge", data);
|
|
161
|
+
}
|
|
162
|
+
// Brand Voice
|
|
163
|
+
async getBrandVoice() {
|
|
164
|
+
return this.request("GET", "/api/v1/brand-voice");
|
|
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
|
+
}
|
|
155
213
|
// RSS
|
|
156
214
|
async fetchFeed(url, limit) {
|
|
157
215
|
const params = { url };
|
|
@@ -161,6 +219,16 @@ var BuzzPosterClient = class {
|
|
|
161
219
|
async fetchArticle(url) {
|
|
162
220
|
return this.request("GET", "/api/v1/rss/article", void 0, { url });
|
|
163
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
|
+
}
|
|
164
232
|
};
|
|
165
233
|
|
|
166
234
|
// src/tools/posts.ts
|
|
@@ -168,16 +236,36 @@ import { z } from "zod";
|
|
|
168
236
|
function registerPostTools(server2, client2) {
|
|
169
237
|
server2.tool(
|
|
170
238
|
"post",
|
|
171
|
-
"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.",
|
|
172
240
|
{
|
|
173
241
|
content: z.string().optional().describe("The text content of the post"),
|
|
174
242
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
175
243
|
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
176
244
|
platform_specific: z.record(z.record(z.unknown())).optional().describe(
|
|
177
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."
|
|
178
249
|
)
|
|
179
250
|
},
|
|
251
|
+
{
|
|
252
|
+
title: "Publish Post Now",
|
|
253
|
+
readOnlyHint: false,
|
|
254
|
+
destructiveHint: false,
|
|
255
|
+
idempotentHint: false,
|
|
256
|
+
openWorldHint: true
|
|
257
|
+
},
|
|
180
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
|
+
}
|
|
181
269
|
const platforms = args.platforms.map((platform) => {
|
|
182
270
|
const entry = { platform };
|
|
183
271
|
if (args.platform_specific?.[platform]) {
|
|
@@ -204,10 +292,20 @@ function registerPostTools(server2, client2) {
|
|
|
204
292
|
);
|
|
205
293
|
server2.tool(
|
|
206
294
|
"cross_post",
|
|
207
|
-
"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.",
|
|
208
296
|
{
|
|
209
297
|
content: z.string().describe("The text content to post everywhere"),
|
|
210
|
-
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
|
|
211
309
|
},
|
|
212
310
|
async (args) => {
|
|
213
311
|
const accountsData = await client2.listAccounts();
|
|
@@ -224,6 +322,16 @@ function registerPostTools(server2, client2) {
|
|
|
224
322
|
]
|
|
225
323
|
};
|
|
226
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
|
+
}
|
|
227
335
|
const body = {
|
|
228
336
|
content: args.content,
|
|
229
337
|
platforms: platforms.map((p) => ({ platform: p })),
|
|
@@ -243,16 +351,37 @@ function registerPostTools(server2, client2) {
|
|
|
243
351
|
);
|
|
244
352
|
server2.tool(
|
|
245
353
|
"schedule_post",
|
|
246
|
-
"Schedule a post for future publication.",
|
|
354
|
+
"Schedule a post for future publication. Requires confirmation before scheduling.",
|
|
247
355
|
{
|
|
248
356
|
content: z.string().optional().describe("The text content of the post"),
|
|
249
357
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
250
358
|
scheduled_for: z.string().describe("ISO 8601 datetime for when to publish, e.g. 2024-01-16T12:00:00"),
|
|
251
359
|
timezone: z.string().default("UTC").describe("Timezone for the scheduled time, e.g. America/New_York"),
|
|
252
360
|
media_urls: z.array(z.string()).optional().describe("Media URLs to attach"),
|
|
253
|
-
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
|
|
254
372
|
},
|
|
255
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
|
+
}
|
|
256
385
|
const platforms = args.platforms.map((platform) => {
|
|
257
386
|
const entry = { platform };
|
|
258
387
|
if (args.platform_specific?.[platform]) {
|
|
@@ -286,6 +415,13 @@ function registerPostTools(server2, client2) {
|
|
|
286
415
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
287
416
|
media_urls: z.array(z.string()).optional().describe("Media URLs to attach")
|
|
288
417
|
},
|
|
418
|
+
{
|
|
419
|
+
title: "Create Draft Post",
|
|
420
|
+
readOnlyHint: false,
|
|
421
|
+
destructiveHint: false,
|
|
422
|
+
idempotentHint: false,
|
|
423
|
+
openWorldHint: false
|
|
424
|
+
},
|
|
289
425
|
async (args) => {
|
|
290
426
|
const body = {
|
|
291
427
|
content: args.content,
|
|
@@ -311,6 +447,13 @@ function registerPostTools(server2, client2) {
|
|
|
311
447
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
312
448
|
limit: z.string().optional().describe("Number of posts to return")
|
|
313
449
|
},
|
|
450
|
+
{
|
|
451
|
+
title: "List Posts",
|
|
452
|
+
readOnlyHint: true,
|
|
453
|
+
destructiveHint: false,
|
|
454
|
+
idempotentHint: true,
|
|
455
|
+
openWorldHint: true
|
|
456
|
+
},
|
|
314
457
|
async (args) => {
|
|
315
458
|
const params = {};
|
|
316
459
|
if (args.status) params.status = args.status;
|
|
@@ -327,6 +470,13 @@ function registerPostTools(server2, client2) {
|
|
|
327
470
|
{
|
|
328
471
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
329
472
|
},
|
|
473
|
+
{
|
|
474
|
+
title: "Get Post Details",
|
|
475
|
+
readOnlyHint: true,
|
|
476
|
+
destructiveHint: false,
|
|
477
|
+
idempotentHint: true,
|
|
478
|
+
openWorldHint: true
|
|
479
|
+
},
|
|
330
480
|
async (args) => {
|
|
331
481
|
const result = await client2.getPost(args.post_id);
|
|
332
482
|
return {
|
|
@@ -336,11 +486,48 @@ function registerPostTools(server2, client2) {
|
|
|
336
486
|
);
|
|
337
487
|
server2.tool(
|
|
338
488
|
"retry_post",
|
|
339
|
-
"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.",
|
|
340
490
|
{
|
|
341
|
-
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
|
|
342
502
|
},
|
|
343
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
|
+
}
|
|
344
531
|
const result = await client2.retryPost(args.post_id);
|
|
345
532
|
return {
|
|
346
533
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -355,6 +542,13 @@ function registerAccountTools(server2, client2) {
|
|
|
355
542
|
"list_accounts",
|
|
356
543
|
"List all connected social media accounts with their platform, username, and connection status.",
|
|
357
544
|
{},
|
|
545
|
+
{
|
|
546
|
+
title: "List Connected Accounts",
|
|
547
|
+
readOnlyHint: true,
|
|
548
|
+
destructiveHint: false,
|
|
549
|
+
idempotentHint: true,
|
|
550
|
+
openWorldHint: false
|
|
551
|
+
},
|
|
358
552
|
async () => {
|
|
359
553
|
const result = await client2.listAccounts();
|
|
360
554
|
return {
|
|
@@ -364,12 +558,49 @@ function registerAccountTools(server2, client2) {
|
|
|
364
558
|
);
|
|
365
559
|
server2.tool(
|
|
366
560
|
"check_accounts_health",
|
|
367
|
-
"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.",
|
|
368
562
|
{},
|
|
563
|
+
{
|
|
564
|
+
title: "Check Account Health",
|
|
565
|
+
readOnlyHint: true,
|
|
566
|
+
destructiveHint: false,
|
|
567
|
+
idempotentHint: true,
|
|
568
|
+
openWorldHint: true
|
|
569
|
+
},
|
|
369
570
|
async () => {
|
|
370
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
|
+
}
|
|
371
602
|
return {
|
|
372
|
-
content: [{ type: "text", text
|
|
603
|
+
content: [{ type: "text", text }]
|
|
373
604
|
};
|
|
374
605
|
}
|
|
375
606
|
);
|
|
@@ -386,6 +617,13 @@ function registerAnalyticsTools(server2, client2) {
|
|
|
386
617
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
387
618
|
to_date: z2.string().optional().describe("End date for analytics range (ISO 8601)")
|
|
388
619
|
},
|
|
620
|
+
{
|
|
621
|
+
title: "Get Analytics",
|
|
622
|
+
readOnlyHint: true,
|
|
623
|
+
destructiveHint: false,
|
|
624
|
+
idempotentHint: true,
|
|
625
|
+
openWorldHint: true
|
|
626
|
+
},
|
|
389
627
|
async (args) => {
|
|
390
628
|
const params = {};
|
|
391
629
|
if (args.platform) params.platform = args.platform;
|
|
@@ -408,6 +646,13 @@ function registerInboxTools(server2, client2) {
|
|
|
408
646
|
{
|
|
409
647
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
410
648
|
},
|
|
649
|
+
{
|
|
650
|
+
title: "List Conversations",
|
|
651
|
+
readOnlyHint: true,
|
|
652
|
+
destructiveHint: false,
|
|
653
|
+
idempotentHint: true,
|
|
654
|
+
openWorldHint: true
|
|
655
|
+
},
|
|
411
656
|
async (args) => {
|
|
412
657
|
const params = {};
|
|
413
658
|
if (args.platform) params.platform = args.platform;
|
|
@@ -423,6 +668,13 @@ function registerInboxTools(server2, client2) {
|
|
|
423
668
|
{
|
|
424
669
|
conversation_id: z3.string().describe("The conversation ID")
|
|
425
670
|
},
|
|
671
|
+
{
|
|
672
|
+
title: "Get Conversation Messages",
|
|
673
|
+
readOnlyHint: true,
|
|
674
|
+
destructiveHint: false,
|
|
675
|
+
idempotentHint: true,
|
|
676
|
+
openWorldHint: true
|
|
677
|
+
},
|
|
426
678
|
async (args) => {
|
|
427
679
|
const result = await client2.getConversation(args.conversation_id);
|
|
428
680
|
return {
|
|
@@ -432,12 +684,29 @@ function registerInboxTools(server2, client2) {
|
|
|
432
684
|
);
|
|
433
685
|
server2.tool(
|
|
434
686
|
"reply_to_conversation",
|
|
435
|
-
"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.",
|
|
436
688
|
{
|
|
437
689
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
438
|
-
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
|
|
439
699
|
},
|
|
440
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
|
+
}
|
|
441
710
|
const result = await client2.replyToConversation(
|
|
442
711
|
args.conversation_id,
|
|
443
712
|
args.message
|
|
@@ -453,6 +722,13 @@ function registerInboxTools(server2, client2) {
|
|
|
453
722
|
{
|
|
454
723
|
post_id: z3.string().optional().describe("Filter comments by post ID")
|
|
455
724
|
},
|
|
725
|
+
{
|
|
726
|
+
title: "List Comments",
|
|
727
|
+
readOnlyHint: true,
|
|
728
|
+
destructiveHint: false,
|
|
729
|
+
idempotentHint: true,
|
|
730
|
+
openWorldHint: true
|
|
731
|
+
},
|
|
456
732
|
async (args) => {
|
|
457
733
|
const params = {};
|
|
458
734
|
if (args.post_id) params.postId = args.post_id;
|
|
@@ -464,12 +740,29 @@ function registerInboxTools(server2, client2) {
|
|
|
464
740
|
);
|
|
465
741
|
server2.tool(
|
|
466
742
|
"reply_to_comment",
|
|
467
|
-
"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.",
|
|
468
744
|
{
|
|
469
745
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
470
|
-
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
|
|
471
755
|
},
|
|
472
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
|
+
}
|
|
473
766
|
const result = await client2.replyToComment(
|
|
474
767
|
args.comment_id,
|
|
475
768
|
args.message
|
|
@@ -483,6 +776,13 @@ function registerInboxTools(server2, client2) {
|
|
|
483
776
|
"list_reviews",
|
|
484
777
|
"List reviews on your Facebook page.",
|
|
485
778
|
{},
|
|
779
|
+
{
|
|
780
|
+
title: "List Reviews",
|
|
781
|
+
readOnlyHint: true,
|
|
782
|
+
destructiveHint: false,
|
|
783
|
+
idempotentHint: true,
|
|
784
|
+
openWorldHint: true
|
|
785
|
+
},
|
|
486
786
|
async () => {
|
|
487
787
|
const result = await client2.listReviews();
|
|
488
788
|
return {
|
|
@@ -492,12 +792,29 @@ function registerInboxTools(server2, client2) {
|
|
|
492
792
|
);
|
|
493
793
|
server2.tool(
|
|
494
794
|
"reply_to_review",
|
|
495
|
-
"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.",
|
|
496
796
|
{
|
|
497
797
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
498
|
-
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
|
|
499
807
|
},
|
|
500
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
|
+
}
|
|
501
818
|
const result = await client2.replyToReview(
|
|
502
819
|
args.review_id,
|
|
503
820
|
args.message
|
|
@@ -519,6 +836,13 @@ function registerMediaTools(server2, client2) {
|
|
|
519
836
|
{
|
|
520
837
|
file_path: z4.string().describe("Absolute path to the file on the local filesystem")
|
|
521
838
|
},
|
|
839
|
+
{
|
|
840
|
+
title: "Upload Media File",
|
|
841
|
+
readOnlyHint: false,
|
|
842
|
+
destructiveHint: false,
|
|
843
|
+
idempotentHint: false,
|
|
844
|
+
openWorldHint: false
|
|
845
|
+
},
|
|
522
846
|
async (args) => {
|
|
523
847
|
const buffer = await readFile(args.file_path);
|
|
524
848
|
const base64 = buffer.toString("base64");
|
|
@@ -551,6 +875,13 @@ function registerMediaTools(server2, client2) {
|
|
|
551
875
|
data: z4.string().describe("Base64-encoded file data"),
|
|
552
876
|
mime_type: z4.string().describe("MIME type of the file, e.g. image/jpeg, video/mp4")
|
|
553
877
|
},
|
|
878
|
+
{
|
|
879
|
+
title: "Upload Media Base64",
|
|
880
|
+
readOnlyHint: false,
|
|
881
|
+
destructiveHint: false,
|
|
882
|
+
idempotentHint: false,
|
|
883
|
+
openWorldHint: false
|
|
884
|
+
},
|
|
554
885
|
async (args) => {
|
|
555
886
|
const result = await client2.uploadMedia(
|
|
556
887
|
args.filename,
|
|
@@ -566,6 +897,13 @@ function registerMediaTools(server2, client2) {
|
|
|
566
897
|
"list_media",
|
|
567
898
|
"List all uploaded media files in your BuzzPoster media library.",
|
|
568
899
|
{},
|
|
900
|
+
{
|
|
901
|
+
title: "List Media Library",
|
|
902
|
+
readOnlyHint: true,
|
|
903
|
+
destructiveHint: false,
|
|
904
|
+
idempotentHint: true,
|
|
905
|
+
openWorldHint: false
|
|
906
|
+
},
|
|
569
907
|
async () => {
|
|
570
908
|
const result = await client2.listMedia();
|
|
571
909
|
return {
|
|
@@ -575,11 +913,27 @@ function registerMediaTools(server2, client2) {
|
|
|
575
913
|
);
|
|
576
914
|
server2.tool(
|
|
577
915
|
"delete_media",
|
|
578
|
-
"Delete a media file from your BuzzPoster media library.",
|
|
916
|
+
"Delete a media file from your BuzzPoster media library. This cannot be undone.",
|
|
579
917
|
{
|
|
580
|
-
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
|
|
581
927
|
},
|
|
582
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
|
+
}
|
|
583
937
|
await client2.deleteMedia(args.key);
|
|
584
938
|
return {
|
|
585
939
|
content: [{ type: "text", text: "Media deleted successfully." }]
|
|
@@ -598,6 +952,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
598
952
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
599
953
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
600
954
|
},
|
|
955
|
+
{
|
|
956
|
+
title: "List Subscribers",
|
|
957
|
+
readOnlyHint: true,
|
|
958
|
+
destructiveHint: false,
|
|
959
|
+
idempotentHint: true,
|
|
960
|
+
openWorldHint: true
|
|
961
|
+
},
|
|
601
962
|
async (args) => {
|
|
602
963
|
const params = {};
|
|
603
964
|
if (args.page) params.page = args.page;
|
|
@@ -616,6 +977,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
616
977
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
617
978
|
last_name: z5.string().optional().describe("Subscriber's last name")
|
|
618
979
|
},
|
|
980
|
+
{
|
|
981
|
+
title: "Add Subscriber",
|
|
982
|
+
readOnlyHint: false,
|
|
983
|
+
destructiveHint: false,
|
|
984
|
+
idempotentHint: false,
|
|
985
|
+
openWorldHint: true
|
|
986
|
+
},
|
|
619
987
|
async (args) => {
|
|
620
988
|
const result = await client2.addSubscriber({
|
|
621
989
|
email: args.email,
|
|
@@ -635,6 +1003,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
635
1003
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
636
1004
|
preview_text: z5.string().optional().describe("Preview text shown in email clients")
|
|
637
1005
|
},
|
|
1006
|
+
{
|
|
1007
|
+
title: "Create Newsletter Draft",
|
|
1008
|
+
readOnlyHint: false,
|
|
1009
|
+
destructiveHint: false,
|
|
1010
|
+
idempotentHint: false,
|
|
1011
|
+
openWorldHint: true
|
|
1012
|
+
},
|
|
638
1013
|
async (args) => {
|
|
639
1014
|
const result = await client2.createBroadcast({
|
|
640
1015
|
subject: args.subject,
|
|
@@ -655,6 +1030,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
655
1030
|
content: z5.string().optional().describe("Updated HTML content"),
|
|
656
1031
|
preview_text: z5.string().optional().describe("Updated preview text")
|
|
657
1032
|
},
|
|
1033
|
+
{
|
|
1034
|
+
title: "Update Newsletter Draft",
|
|
1035
|
+
readOnlyHint: false,
|
|
1036
|
+
destructiveHint: false,
|
|
1037
|
+
idempotentHint: true,
|
|
1038
|
+
openWorldHint: true
|
|
1039
|
+
},
|
|
658
1040
|
async (args) => {
|
|
659
1041
|
const data = {};
|
|
660
1042
|
if (args.subject) data.subject = args.subject;
|
|
@@ -668,11 +1050,31 @@ function registerNewsletterTools(server2, client2) {
|
|
|
668
1050
|
);
|
|
669
1051
|
server2.tool(
|
|
670
1052
|
"send_newsletter",
|
|
671
|
-
"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.",
|
|
672
1054
|
{
|
|
673
|
-
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send")
|
|
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
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
title: "Send Newsletter",
|
|
1062
|
+
readOnlyHint: false,
|
|
1063
|
+
destructiveHint: false,
|
|
1064
|
+
idempotentHint: false,
|
|
1065
|
+
openWorldHint: true
|
|
674
1066
|
},
|
|
675
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
|
+
}
|
|
676
1078
|
await client2.sendBroadcast(args.broadcast_id);
|
|
677
1079
|
return {
|
|
678
1080
|
content: [{ type: "text", text: "Newsletter sent successfully." }]
|
|
@@ -685,6 +1087,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
685
1087
|
{
|
|
686
1088
|
page: z5.string().optional().describe("Page number for pagination")
|
|
687
1089
|
},
|
|
1090
|
+
{
|
|
1091
|
+
title: "List Newsletters",
|
|
1092
|
+
readOnlyHint: true,
|
|
1093
|
+
destructiveHint: false,
|
|
1094
|
+
idempotentHint: true,
|
|
1095
|
+
openWorldHint: true
|
|
1096
|
+
},
|
|
688
1097
|
async (args) => {
|
|
689
1098
|
const params = {};
|
|
690
1099
|
if (args.page) params.page = args.page;
|
|
@@ -698,6 +1107,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
698
1107
|
"list_tags",
|
|
699
1108
|
"List all subscriber tags from your email service provider.",
|
|
700
1109
|
{},
|
|
1110
|
+
{
|
|
1111
|
+
title: "List Subscriber Tags",
|
|
1112
|
+
readOnlyHint: true,
|
|
1113
|
+
destructiveHint: false,
|
|
1114
|
+
idempotentHint: true,
|
|
1115
|
+
openWorldHint: true
|
|
1116
|
+
},
|
|
701
1117
|
async () => {
|
|
702
1118
|
const result = await client2.listTags();
|
|
703
1119
|
return {
|
|
@@ -709,6 +1125,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
709
1125
|
"list_sequences",
|
|
710
1126
|
"List all email sequences/automations from your email service provider.",
|
|
711
1127
|
{},
|
|
1128
|
+
{
|
|
1129
|
+
title: "List Email Sequences",
|
|
1130
|
+
readOnlyHint: true,
|
|
1131
|
+
destructiveHint: false,
|
|
1132
|
+
idempotentHint: true,
|
|
1133
|
+
openWorldHint: true
|
|
1134
|
+
},
|
|
712
1135
|
async () => {
|
|
713
1136
|
const result = await client2.listSequences();
|
|
714
1137
|
return {
|
|
@@ -720,6 +1143,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
720
1143
|
"list_forms",
|
|
721
1144
|
"List all signup forms from your email service provider.",
|
|
722
1145
|
{},
|
|
1146
|
+
{
|
|
1147
|
+
title: "List Signup Forms",
|
|
1148
|
+
readOnlyHint: true,
|
|
1149
|
+
destructiveHint: false,
|
|
1150
|
+
idempotentHint: true,
|
|
1151
|
+
openWorldHint: true
|
|
1152
|
+
},
|
|
723
1153
|
async () => {
|
|
724
1154
|
const result = await client2.listForms();
|
|
725
1155
|
return {
|
|
@@ -739,6 +1169,13 @@ function registerRssTools(server2, client2) {
|
|
|
739
1169
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
740
1170
|
limit: z6.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
741
1171
|
},
|
|
1172
|
+
{
|
|
1173
|
+
title: "Fetch RSS Feed",
|
|
1174
|
+
readOnlyHint: true,
|
|
1175
|
+
destructiveHint: false,
|
|
1176
|
+
idempotentHint: true,
|
|
1177
|
+
openWorldHint: true
|
|
1178
|
+
},
|
|
742
1179
|
async (args) => {
|
|
743
1180
|
const result = await client2.fetchFeed(args.url, args.limit);
|
|
744
1181
|
return {
|
|
@@ -752,6 +1189,13 @@ function registerRssTools(server2, client2) {
|
|
|
752
1189
|
{
|
|
753
1190
|
url: z6.string().describe("The article URL to extract content from")
|
|
754
1191
|
},
|
|
1192
|
+
{
|
|
1193
|
+
title: "Fetch Article Content",
|
|
1194
|
+
readOnlyHint: true,
|
|
1195
|
+
destructiveHint: false,
|
|
1196
|
+
idempotentHint: true,
|
|
1197
|
+
openWorldHint: true
|
|
1198
|
+
},
|
|
755
1199
|
async (args) => {
|
|
756
1200
|
const result = await client2.fetchArticle(args.url);
|
|
757
1201
|
return {
|
|
@@ -767,6 +1211,13 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
767
1211
|
"get_account",
|
|
768
1212
|
"Get your BuzzPoster account details including name, email, subscription status, ESP configuration, and connected platforms.",
|
|
769
1213
|
{},
|
|
1214
|
+
{
|
|
1215
|
+
title: "Get Account Details",
|
|
1216
|
+
readOnlyHint: true,
|
|
1217
|
+
destructiveHint: false,
|
|
1218
|
+
idempotentHint: true,
|
|
1219
|
+
openWorldHint: false
|
|
1220
|
+
},
|
|
770
1221
|
async () => {
|
|
771
1222
|
const result = await client2.getAccount();
|
|
772
1223
|
return {
|
|
@@ -776,6 +1227,894 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
776
1227
|
);
|
|
777
1228
|
}
|
|
778
1229
|
|
|
1230
|
+
// src/tools/brand-voice.ts
|
|
1231
|
+
function registerBrandVoiceTools(server2, client2) {
|
|
1232
|
+
server2.tool(
|
|
1233
|
+
"get_brand_voice",
|
|
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.",
|
|
1235
|
+
{},
|
|
1236
|
+
{
|
|
1237
|
+
title: "Get Brand Voice",
|
|
1238
|
+
readOnlyHint: true,
|
|
1239
|
+
destructiveHint: false,
|
|
1240
|
+
idempotentHint: true,
|
|
1241
|
+
openWorldHint: false
|
|
1242
|
+
},
|
|
1243
|
+
async () => {
|
|
1244
|
+
try {
|
|
1245
|
+
const voice = await client2.getBrandVoice();
|
|
1246
|
+
const lines = [];
|
|
1247
|
+
lines.push(`## Brand Voice: ${voice.name || "My Brand"}`);
|
|
1248
|
+
lines.push("");
|
|
1249
|
+
if (voice.description) {
|
|
1250
|
+
lines.push("### Voice Description");
|
|
1251
|
+
lines.push(voice.description);
|
|
1252
|
+
lines.push("");
|
|
1253
|
+
}
|
|
1254
|
+
if (voice.dos && voice.dos.length > 0) {
|
|
1255
|
+
lines.push("### Do's");
|
|
1256
|
+
for (const rule of voice.dos) {
|
|
1257
|
+
lines.push(`- ${rule}`);
|
|
1258
|
+
}
|
|
1259
|
+
lines.push("");
|
|
1260
|
+
}
|
|
1261
|
+
if (voice.donts && voice.donts.length > 0) {
|
|
1262
|
+
lines.push("### Don'ts");
|
|
1263
|
+
for (const rule of voice.donts) {
|
|
1264
|
+
lines.push(`- ${rule}`);
|
|
1265
|
+
}
|
|
1266
|
+
lines.push("");
|
|
1267
|
+
}
|
|
1268
|
+
if (voice.platformRules && Object.keys(voice.platformRules).length > 0) {
|
|
1269
|
+
lines.push("### Platform-Specific Rules");
|
|
1270
|
+
for (const [platform, rules] of Object.entries(voice.platformRules)) {
|
|
1271
|
+
lines.push(`**${platform}:** ${rules}`);
|
|
1272
|
+
}
|
|
1273
|
+
lines.push("");
|
|
1274
|
+
}
|
|
1275
|
+
if (voice.examplePosts && voice.examplePosts.length > 0) {
|
|
1276
|
+
lines.push("### Example Posts");
|
|
1277
|
+
voice.examplePosts.forEach((post, i) => {
|
|
1278
|
+
lines.push(`${i + 1}. ${post}`);
|
|
1279
|
+
});
|
|
1280
|
+
lines.push("");
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1284
|
+
};
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1287
|
+
if (message.includes("404") || message.includes("No brand voice")) {
|
|
1288
|
+
return {
|
|
1289
|
+
content: [
|
|
1290
|
+
{
|
|
1291
|
+
type: "text",
|
|
1292
|
+
text: "No brand voice has been configured yet. The customer can set one up at their BuzzPoster dashboard."
|
|
1293
|
+
}
|
|
1294
|
+
]
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
throw error;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/tools/knowledge.ts
|
|
1304
|
+
import { z as z7 } from "zod";
|
|
1305
|
+
function registerKnowledgeTools(server2, client2) {
|
|
1306
|
+
server2.tool(
|
|
1307
|
+
"get_knowledge_base",
|
|
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.",
|
|
1309
|
+
{},
|
|
1310
|
+
{
|
|
1311
|
+
title: "Get Knowledge Base",
|
|
1312
|
+
readOnlyHint: true,
|
|
1313
|
+
destructiveHint: false,
|
|
1314
|
+
idempotentHint: true,
|
|
1315
|
+
openWorldHint: false
|
|
1316
|
+
},
|
|
1317
|
+
async () => {
|
|
1318
|
+
try {
|
|
1319
|
+
const data = await client2.listKnowledge();
|
|
1320
|
+
const items = data.items ?? [];
|
|
1321
|
+
if (items.length === 0) {
|
|
1322
|
+
return {
|
|
1323
|
+
content: [
|
|
1324
|
+
{
|
|
1325
|
+
type: "text",
|
|
1326
|
+
text: "The knowledge base is empty. The customer can add reference material at their BuzzPoster dashboard."
|
|
1327
|
+
}
|
|
1328
|
+
]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
const totalChars = items.reduce(
|
|
1332
|
+
(sum, item) => sum + item.content.length,
|
|
1333
|
+
0
|
|
1334
|
+
);
|
|
1335
|
+
const shouldTruncate = totalChars > 1e4;
|
|
1336
|
+
const lines = [];
|
|
1337
|
+
lines.push(`## Knowledge Base (${items.length} items)`);
|
|
1338
|
+
lines.push("");
|
|
1339
|
+
for (const item of items) {
|
|
1340
|
+
lines.push(`### ${item.title}`);
|
|
1341
|
+
if (item.tags && item.tags.length > 0) {
|
|
1342
|
+
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1343
|
+
}
|
|
1344
|
+
if (shouldTruncate) {
|
|
1345
|
+
lines.push(item.content.slice(0, 500) + " [truncated]");
|
|
1346
|
+
} else {
|
|
1347
|
+
lines.push(item.content);
|
|
1348
|
+
}
|
|
1349
|
+
lines.push("");
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1353
|
+
};
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1356
|
+
throw new Error(`Failed to get knowledge base: ${message}`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
server2.tool(
|
|
1361
|
+
"search_knowledge",
|
|
1362
|
+
"Search the customer's knowledge base by tag or keyword. Use this to find specific reference material when writing about a topic.",
|
|
1363
|
+
{
|
|
1364
|
+
query: z7.string().describe(
|
|
1365
|
+
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
1366
|
+
)
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
title: "Search Knowledge Base",
|
|
1370
|
+
readOnlyHint: true,
|
|
1371
|
+
destructiveHint: false,
|
|
1372
|
+
idempotentHint: true,
|
|
1373
|
+
openWorldHint: false
|
|
1374
|
+
},
|
|
1375
|
+
async ({ query }) => {
|
|
1376
|
+
try {
|
|
1377
|
+
const data = await client2.listKnowledge({ tag: query });
|
|
1378
|
+
const items = data.items ?? [];
|
|
1379
|
+
if (items.length === 0) {
|
|
1380
|
+
return {
|
|
1381
|
+
content: [
|
|
1382
|
+
{
|
|
1383
|
+
type: "text",
|
|
1384
|
+
text: `No knowledge base items found matching "${query}".`
|
|
1385
|
+
}
|
|
1386
|
+
]
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
const lines = [];
|
|
1390
|
+
lines.push(
|
|
1391
|
+
`## Knowledge Base Results for "${query}" (${items.length} items)`
|
|
1392
|
+
);
|
|
1393
|
+
lines.push("");
|
|
1394
|
+
for (const item of items) {
|
|
1395
|
+
lines.push(`### ${item.title}`);
|
|
1396
|
+
if (item.tags && item.tags.length > 0) {
|
|
1397
|
+
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1398
|
+
}
|
|
1399
|
+
lines.push(item.content);
|
|
1400
|
+
lines.push("");
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1404
|
+
};
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1407
|
+
throw new Error(`Failed to search knowledge base: ${message}`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
server2.tool(
|
|
1412
|
+
"add_knowledge",
|
|
1413
|
+
"Add a new item to the customer's knowledge base. Use this to save useful information the customer shares during conversation.",
|
|
1414
|
+
{
|
|
1415
|
+
title: z7.string().describe("Title for the knowledge item"),
|
|
1416
|
+
content: z7.string().describe("The content/text to save"),
|
|
1417
|
+
tags: z7.array(z7.string()).optional().describe("Optional tags for categorization")
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
title: "Add Knowledge Item",
|
|
1421
|
+
readOnlyHint: false,
|
|
1422
|
+
destructiveHint: false,
|
|
1423
|
+
idempotentHint: false,
|
|
1424
|
+
openWorldHint: false
|
|
1425
|
+
},
|
|
1426
|
+
async ({ title, content, tags }) => {
|
|
1427
|
+
try {
|
|
1428
|
+
await client2.createKnowledge({
|
|
1429
|
+
title,
|
|
1430
|
+
content,
|
|
1431
|
+
sourceType: "text",
|
|
1432
|
+
tags: tags ?? []
|
|
1433
|
+
});
|
|
1434
|
+
return {
|
|
1435
|
+
content: [
|
|
1436
|
+
{
|
|
1437
|
+
type: "text",
|
|
1438
|
+
text: `Added "${title}" to the knowledge base.`
|
|
1439
|
+
}
|
|
1440
|
+
]
|
|
1441
|
+
};
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1444
|
+
throw new Error(`Failed to add knowledge item: ${message}`);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
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
|
+
|
|
779
2118
|
// src/index.ts
|
|
780
2119
|
var apiKey = process.env.BUZZPOSTER_API_KEY;
|
|
781
2120
|
var apiUrl = process.env.BUZZPOSTER_API_URL ?? "https://api.buzzposter.com";
|
|
@@ -801,5 +2140,11 @@ registerMediaTools(server, client);
|
|
|
801
2140
|
registerNewsletterTools(server, client);
|
|
802
2141
|
registerRssTools(server, client);
|
|
803
2142
|
registerAccountInfoTool(server, client);
|
|
2143
|
+
registerBrandVoiceTools(server, client);
|
|
2144
|
+
registerKnowledgeTools(server, client);
|
|
2145
|
+
registerAudienceTools(server, client);
|
|
2146
|
+
registerNewsletterTemplateTools(server, client);
|
|
2147
|
+
registerCalendarTools(server, client);
|
|
2148
|
+
registerNotificationTools(server, client);
|
|
804
2149
|
var transport = new StdioServerTransport();
|
|
805
2150
|
await server.connect(transport);
|