@buzzposter/mcp 0.1.2 → 0.1.5
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 +1459 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -101,12 +101,28 @@ var BuzzPosterClient = class {
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
// Media
|
|
104
|
-
async
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
async uploadMediaMultipart(filename, buffer, mimeType) {
|
|
105
|
+
const formData = new FormData();
|
|
106
|
+
formData.append("file", new Blob([buffer], { type: mimeType }), filename);
|
|
107
|
+
const url = new URL(`${this.baseUrl}/api/v1/media/upload`);
|
|
108
|
+
const res = await fetch(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
111
|
+
body: formData
|
|
109
112
|
});
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const errorBody = await res.json().catch(() => ({ message: res.statusText }));
|
|
115
|
+
const message = typeof errorBody === "object" && errorBody !== null && "message" in errorBody ? String(errorBody.message) : `API error (${res.status})`;
|
|
116
|
+
throw new Error(`BuzzPoster API error (${res.status}): ${message}`);
|
|
117
|
+
}
|
|
118
|
+
const text = await res.text();
|
|
119
|
+
return text ? JSON.parse(text) : void 0;
|
|
120
|
+
}
|
|
121
|
+
async uploadFromUrl(data) {
|
|
122
|
+
return this.request("POST", "/api/v1/media/upload-from-url", data);
|
|
123
|
+
}
|
|
124
|
+
async getUploadUrl(data) {
|
|
125
|
+
return this.request("POST", "/api/v1/media/presign", data);
|
|
110
126
|
}
|
|
111
127
|
async listMedia() {
|
|
112
128
|
return this.request("GET", "/api/v1/media");
|
|
@@ -152,6 +168,64 @@ var BuzzPosterClient = class {
|
|
|
152
168
|
async listForms() {
|
|
153
169
|
return this.request("GET", "/api/v1/newsletters/forms");
|
|
154
170
|
}
|
|
171
|
+
// Knowledge
|
|
172
|
+
async listKnowledge(params) {
|
|
173
|
+
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
174
|
+
}
|
|
175
|
+
async createKnowledge(data) {
|
|
176
|
+
return this.request("POST", "/api/v1/knowledge", data);
|
|
177
|
+
}
|
|
178
|
+
// Brand Voice
|
|
179
|
+
async getBrandVoice() {
|
|
180
|
+
return this.request("GET", "/api/v1/brand-voice");
|
|
181
|
+
}
|
|
182
|
+
// Audiences
|
|
183
|
+
async getAudience(id) {
|
|
184
|
+
return this.request("GET", `/api/v1/audiences/${id}`);
|
|
185
|
+
}
|
|
186
|
+
async getDefaultAudience() {
|
|
187
|
+
return this.request("GET", "/api/v1/audiences/default");
|
|
188
|
+
}
|
|
189
|
+
// Newsletter Templates
|
|
190
|
+
async getTemplate(id) {
|
|
191
|
+
if (id) return this.request("GET", `/api/v1/templates/${id}`);
|
|
192
|
+
return this.request("GET", "/api/v1/templates/default");
|
|
193
|
+
}
|
|
194
|
+
async listTemplates() {
|
|
195
|
+
return this.request("GET", "/api/v1/templates");
|
|
196
|
+
}
|
|
197
|
+
// Newsletter Archive
|
|
198
|
+
async listNewsletterArchive(params) {
|
|
199
|
+
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
200
|
+
}
|
|
201
|
+
async getArchivedNewsletter(id) {
|
|
202
|
+
return this.request("GET", `/api/v1/newsletter-archive/${id}`);
|
|
203
|
+
}
|
|
204
|
+
async saveNewsletterToArchive(data) {
|
|
205
|
+
return this.request("POST", "/api/v1/newsletter-archive", data);
|
|
206
|
+
}
|
|
207
|
+
// Calendar
|
|
208
|
+
async getCalendar(params) {
|
|
209
|
+
return this.request("GET", "/api/v1/calendar", void 0, params);
|
|
210
|
+
}
|
|
211
|
+
async rescheduleCalendarItem(id, scheduledFor) {
|
|
212
|
+
return this.request("PUT", `/api/v1/calendar/${id}`, {
|
|
213
|
+
scheduled_for: scheduledFor
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async cancelCalendarItem(id) {
|
|
217
|
+
return this.request("DELETE", `/api/v1/calendar/${id}`);
|
|
218
|
+
}
|
|
219
|
+
// Queue
|
|
220
|
+
async getQueue() {
|
|
221
|
+
return this.request("GET", "/api/v1/queue");
|
|
222
|
+
}
|
|
223
|
+
async updateQueue(data) {
|
|
224
|
+
return this.request("PUT", "/api/v1/queue", data);
|
|
225
|
+
}
|
|
226
|
+
async getNextSlot() {
|
|
227
|
+
return this.request("GET", "/api/v1/queue/next");
|
|
228
|
+
}
|
|
155
229
|
// RSS
|
|
156
230
|
async fetchFeed(url, limit) {
|
|
157
231
|
const params = { url };
|
|
@@ -161,6 +235,16 @@ var BuzzPosterClient = class {
|
|
|
161
235
|
async fetchArticle(url) {
|
|
162
236
|
return this.request("GET", "/api/v1/rss/article", void 0, { url });
|
|
163
237
|
}
|
|
238
|
+
// Notifications
|
|
239
|
+
async getNotifications(params) {
|
|
240
|
+
return this.request("GET", "/api/v1/notifications", void 0, params);
|
|
241
|
+
}
|
|
242
|
+
async markNotificationRead(id) {
|
|
243
|
+
return this.request("PUT", `/api/v1/notifications/${id}/read`);
|
|
244
|
+
}
|
|
245
|
+
async markAllNotificationsRead() {
|
|
246
|
+
return this.request("PUT", "/api/v1/notifications/read-all");
|
|
247
|
+
}
|
|
164
248
|
};
|
|
165
249
|
|
|
166
250
|
// src/tools/posts.ts
|
|
@@ -168,16 +252,36 @@ import { z } from "zod";
|
|
|
168
252
|
function registerPostTools(server2, client2) {
|
|
169
253
|
server2.tool(
|
|
170
254
|
"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.",
|
|
255
|
+
"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
256
|
{
|
|
173
257
|
content: z.string().optional().describe("The text content of the post"),
|
|
174
258
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
175
259
|
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
176
260
|
platform_specific: z.record(z.record(z.unknown())).optional().describe(
|
|
177
261
|
"Platform-specific data keyed by platform name. E.g. { twitter: { threadItems: [...] }, instagram: { firstComment: '...' } }"
|
|
262
|
+
),
|
|
263
|
+
confirmed: z.boolean().optional().describe(
|
|
264
|
+
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
178
265
|
)
|
|
179
266
|
},
|
|
267
|
+
{
|
|
268
|
+
title: "Publish Post Now",
|
|
269
|
+
readOnlyHint: false,
|
|
270
|
+
destructiveHint: false,
|
|
271
|
+
idempotentHint: false,
|
|
272
|
+
openWorldHint: true
|
|
273
|
+
},
|
|
180
274
|
async (args) => {
|
|
275
|
+
if (!args.confirmed) {
|
|
276
|
+
const preview = `## Post Preview
|
|
277
|
+
|
|
278
|
+
**Content:** "${args.content ?? "(no text)"}"
|
|
279
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
280
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
281
|
+
` : "") + `
|
|
282
|
+
This will be published **immediately**. Call this tool again with confirmed=true to proceed.`;
|
|
283
|
+
return { content: [{ type: "text", text: preview }] };
|
|
284
|
+
}
|
|
181
285
|
const platforms = args.platforms.map((platform) => {
|
|
182
286
|
const entry = { platform };
|
|
183
287
|
if (args.platform_specific?.[platform]) {
|
|
@@ -204,10 +308,20 @@ function registerPostTools(server2, client2) {
|
|
|
204
308
|
);
|
|
205
309
|
server2.tool(
|
|
206
310
|
"cross_post",
|
|
207
|
-
"Post the same content to all connected social media platforms at once.",
|
|
311
|
+
"Post the same content to all connected social media platforms at once. Requires confirmation before publishing.",
|
|
208
312
|
{
|
|
209
313
|
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")
|
|
314
|
+
media_urls: z.array(z.string()).optional().describe("Public URLs of media to attach"),
|
|
315
|
+
confirmed: z.boolean().optional().describe(
|
|
316
|
+
"Set to true to confirm and publish. If false or missing, returns a preview for user approval."
|
|
317
|
+
)
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
title: "Cross-Post to All Platforms",
|
|
321
|
+
readOnlyHint: false,
|
|
322
|
+
destructiveHint: false,
|
|
323
|
+
idempotentHint: false,
|
|
324
|
+
openWorldHint: true
|
|
211
325
|
},
|
|
212
326
|
async (args) => {
|
|
213
327
|
const accountsData = await client2.listAccounts();
|
|
@@ -224,6 +338,16 @@ function registerPostTools(server2, client2) {
|
|
|
224
338
|
]
|
|
225
339
|
};
|
|
226
340
|
}
|
|
341
|
+
if (!args.confirmed) {
|
|
342
|
+
const preview = `## Cross-Post Preview
|
|
343
|
+
|
|
344
|
+
**Content:** "${args.content}"
|
|
345
|
+
**Platforms:** ${platforms.join(", ")}
|
|
346
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
347
|
+
` : "") + `
|
|
348
|
+
This will be published **immediately** to all connected platforms. Call this tool again with confirmed=true to proceed.`;
|
|
349
|
+
return { content: [{ type: "text", text: preview }] };
|
|
350
|
+
}
|
|
227
351
|
const body = {
|
|
228
352
|
content: args.content,
|
|
229
353
|
platforms: platforms.map((p) => ({ platform: p })),
|
|
@@ -243,16 +367,37 @@ function registerPostTools(server2, client2) {
|
|
|
243
367
|
);
|
|
244
368
|
server2.tool(
|
|
245
369
|
"schedule_post",
|
|
246
|
-
"Schedule a post for future publication.",
|
|
370
|
+
"Schedule a post for future publication. Requires confirmation before scheduling.",
|
|
247
371
|
{
|
|
248
372
|
content: z.string().optional().describe("The text content of the post"),
|
|
249
373
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
250
374
|
scheduled_for: z.string().describe("ISO 8601 datetime for when to publish, e.g. 2024-01-16T12:00:00"),
|
|
251
375
|
timezone: z.string().default("UTC").describe("Timezone for the scheduled time, e.g. America/New_York"),
|
|
252
376
|
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")
|
|
377
|
+
platform_specific: z.record(z.record(z.unknown())).optional().describe("Platform-specific data keyed by platform name"),
|
|
378
|
+
confirmed: z.boolean().optional().describe(
|
|
379
|
+
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
380
|
+
)
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
title: "Schedule Social Post",
|
|
384
|
+
readOnlyHint: false,
|
|
385
|
+
destructiveHint: false,
|
|
386
|
+
idempotentHint: false,
|
|
387
|
+
openWorldHint: true
|
|
254
388
|
},
|
|
255
389
|
async (args) => {
|
|
390
|
+
if (!args.confirmed) {
|
|
391
|
+
const preview = `## Schedule Preview
|
|
392
|
+
|
|
393
|
+
**Content:** "${args.content ?? "(no text)"}"
|
|
394
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
395
|
+
**Scheduled for:** ${args.scheduled_for} (${args.timezone})
|
|
396
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
397
|
+
` : "") + `
|
|
398
|
+
Call this tool again with confirmed=true to schedule.`;
|
|
399
|
+
return { content: [{ type: "text", text: preview }] };
|
|
400
|
+
}
|
|
256
401
|
const platforms = args.platforms.map((platform) => {
|
|
257
402
|
const entry = { platform };
|
|
258
403
|
if (args.platform_specific?.[platform]) {
|
|
@@ -286,6 +431,13 @@ function registerPostTools(server2, client2) {
|
|
|
286
431
|
platforms: z.array(z.string()).describe("Platforms this draft is intended for"),
|
|
287
432
|
media_urls: z.array(z.string()).optional().describe("Media URLs to attach")
|
|
288
433
|
},
|
|
434
|
+
{
|
|
435
|
+
title: "Create Draft Post",
|
|
436
|
+
readOnlyHint: false,
|
|
437
|
+
destructiveHint: false,
|
|
438
|
+
idempotentHint: false,
|
|
439
|
+
openWorldHint: false
|
|
440
|
+
},
|
|
289
441
|
async (args) => {
|
|
290
442
|
const body = {
|
|
291
443
|
content: args.content,
|
|
@@ -311,6 +463,13 @@ function registerPostTools(server2, client2) {
|
|
|
311
463
|
status: z.string().optional().describe("Filter by status: published, scheduled, draft, failed"),
|
|
312
464
|
limit: z.string().optional().describe("Number of posts to return")
|
|
313
465
|
},
|
|
466
|
+
{
|
|
467
|
+
title: "List Posts",
|
|
468
|
+
readOnlyHint: true,
|
|
469
|
+
destructiveHint: false,
|
|
470
|
+
idempotentHint: true,
|
|
471
|
+
openWorldHint: true
|
|
472
|
+
},
|
|
314
473
|
async (args) => {
|
|
315
474
|
const params = {};
|
|
316
475
|
if (args.status) params.status = args.status;
|
|
@@ -327,6 +486,13 @@ function registerPostTools(server2, client2) {
|
|
|
327
486
|
{
|
|
328
487
|
post_id: z.string().describe("The ID of the post to retrieve")
|
|
329
488
|
},
|
|
489
|
+
{
|
|
490
|
+
title: "Get Post Details",
|
|
491
|
+
readOnlyHint: true,
|
|
492
|
+
destructiveHint: false,
|
|
493
|
+
idempotentHint: true,
|
|
494
|
+
openWorldHint: true
|
|
495
|
+
},
|
|
330
496
|
async (args) => {
|
|
331
497
|
const result = await client2.getPost(args.post_id);
|
|
332
498
|
return {
|
|
@@ -336,11 +502,48 @@ function registerPostTools(server2, client2) {
|
|
|
336
502
|
);
|
|
337
503
|
server2.tool(
|
|
338
504
|
"retry_post",
|
|
339
|
-
"Retry
|
|
505
|
+
"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
506
|
{
|
|
341
|
-
post_id: z.string().describe("The ID of the post to retry")
|
|
507
|
+
post_id: z.string().describe("The ID of the post to retry"),
|
|
508
|
+
confirmed: z.boolean().optional().describe(
|
|
509
|
+
"Set to true to confirm retry. If false/missing, shows post details first."
|
|
510
|
+
)
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
title: "Retry Failed Post",
|
|
514
|
+
readOnlyHint: false,
|
|
515
|
+
destructiveHint: false,
|
|
516
|
+
idempotentHint: true,
|
|
517
|
+
openWorldHint: true
|
|
342
518
|
},
|
|
343
519
|
async (args) => {
|
|
520
|
+
if (!args.confirmed) {
|
|
521
|
+
const post = await client2.getPost(args.post_id);
|
|
522
|
+
const platforms = post.platforms ?? [];
|
|
523
|
+
const failed = platforms.filter((p) => p.status === "failed");
|
|
524
|
+
const published = platforms.filter((p) => p.status === "published");
|
|
525
|
+
let text = `## Retry Post Preview
|
|
526
|
+
|
|
527
|
+
`;
|
|
528
|
+
text += `**Post ID:** ${args.post_id}
|
|
529
|
+
`;
|
|
530
|
+
text += `**Content:** "${(post.content ?? "").substring(0, 100)}"
|
|
531
|
+
`;
|
|
532
|
+
text += `**Status:** ${post.status}
|
|
533
|
+
|
|
534
|
+
`;
|
|
535
|
+
if (published.length > 0) {
|
|
536
|
+
text += `**Already published on:** ${published.map((p) => p.platform).join(", ")}
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
if (failed.length > 0) {
|
|
540
|
+
text += `**Failed on:** ${failed.map((p) => `${p.platform} (${p.error ?? "unknown error"})`).join(", ")}
|
|
541
|
+
`;
|
|
542
|
+
}
|
|
543
|
+
text += `
|
|
544
|
+
Retrying will only attempt the failed platforms. Call this tool again with confirmed=true to proceed.`;
|
|
545
|
+
return { content: [{ type: "text", text }] };
|
|
546
|
+
}
|
|
344
547
|
const result = await client2.retryPost(args.post_id);
|
|
345
548
|
return {
|
|
346
549
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -353,23 +556,103 @@ function registerPostTools(server2, client2) {
|
|
|
353
556
|
function registerAccountTools(server2, client2) {
|
|
354
557
|
server2.tool(
|
|
355
558
|
"list_accounts",
|
|
356
|
-
"List all connected social media
|
|
559
|
+
"List all connected accounts \u2014 both social media platforms (Twitter, Instagram, etc.) and email service provider / ESP (Kit, Beehiiv, Mailchimp). Use this whenever the user asks about their accounts, connections, or integrations.",
|
|
357
560
|
{},
|
|
561
|
+
{
|
|
562
|
+
title: "List Connected Accounts",
|
|
563
|
+
readOnlyHint: true,
|
|
564
|
+
destructiveHint: false,
|
|
565
|
+
idempotentHint: true,
|
|
566
|
+
openWorldHint: false
|
|
567
|
+
},
|
|
358
568
|
async () => {
|
|
359
569
|
const result = await client2.listAccounts();
|
|
570
|
+
const accounts = result.accounts ?? [];
|
|
571
|
+
const esp = result.esp;
|
|
572
|
+
if (accounts.length === 0 && !esp) {
|
|
573
|
+
return {
|
|
574
|
+
content: [{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: "## Connected Accounts\n\nNo accounts connected. Connect social media accounts and/or an email service provider via the dashboard."
|
|
577
|
+
}]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
let text = "## Connected Accounts\n\n";
|
|
581
|
+
text += "### Social Media\n";
|
|
582
|
+
if (accounts.length > 0) {
|
|
583
|
+
for (const a of accounts) {
|
|
584
|
+
const icon = a.isActive ? "\u2705" : "\u274C";
|
|
585
|
+
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
586
|
+
const name = a.username || a.displayName || a.platform;
|
|
587
|
+
text += `${icon} **${platform}** \u2014 @${name}
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
text += "No social accounts connected.\n";
|
|
592
|
+
}
|
|
593
|
+
text += "\n### Email Service Provider (ESP)\n";
|
|
594
|
+
if (esp && esp.connected) {
|
|
595
|
+
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
596
|
+
text += `\u2705 **${provider}** \u2014 Connected`;
|
|
597
|
+
if (esp.publicationId) text += ` (Publication: ${esp.publicationId})`;
|
|
598
|
+
text += "\n";
|
|
599
|
+
} else if (esp) {
|
|
600
|
+
const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
|
|
601
|
+
text += `\u26A0\uFE0F **${provider}** \u2014 Provider set but API key missing
|
|
602
|
+
`;
|
|
603
|
+
} else {
|
|
604
|
+
text += "No ESP configured.\n";
|
|
605
|
+
}
|
|
360
606
|
return {
|
|
361
|
-
content: [{ type: "text", text
|
|
607
|
+
content: [{ type: "text", text }]
|
|
362
608
|
};
|
|
363
609
|
}
|
|
364
610
|
);
|
|
365
611
|
server2.tool(
|
|
366
612
|
"check_accounts_health",
|
|
367
|
-
"Check the health
|
|
613
|
+
"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
614
|
{},
|
|
615
|
+
{
|
|
616
|
+
title: "Check Account Health",
|
|
617
|
+
readOnlyHint: true,
|
|
618
|
+
destructiveHint: false,
|
|
619
|
+
idempotentHint: true,
|
|
620
|
+
openWorldHint: true
|
|
621
|
+
},
|
|
369
622
|
async () => {
|
|
370
623
|
const result = await client2.checkAccountsHealth();
|
|
624
|
+
const accounts = result.accounts ?? [];
|
|
625
|
+
const summary = result.summary;
|
|
626
|
+
if (accounts.length === 0) {
|
|
627
|
+
return {
|
|
628
|
+
content: [{
|
|
629
|
+
type: "text",
|
|
630
|
+
text: "## Account Health\n\nNo connected accounts found. Connect accounts first via the dashboard."
|
|
631
|
+
}]
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const statusIcon = {
|
|
635
|
+
healthy: "\u2705",
|
|
636
|
+
warning: "\u26A0\uFE0F",
|
|
637
|
+
error: "\u274C"
|
|
638
|
+
};
|
|
639
|
+
const lines = accounts.map((a) => {
|
|
640
|
+
const icon = statusIcon[a.status] ?? "\u2753";
|
|
641
|
+
const name = a.username ? `@${a.username}` : a.platform;
|
|
642
|
+
const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
|
|
643
|
+
const msg = a.message || (a.status === "healthy" ? "Healthy" : a.status);
|
|
644
|
+
return `${icon} **${platform}** (${name}) \u2014 ${msg}`;
|
|
645
|
+
});
|
|
646
|
+
let text = `## Account Health
|
|
647
|
+
|
|
648
|
+
${lines.join("\n")}`;
|
|
649
|
+
if (summary) {
|
|
650
|
+
text += `
|
|
651
|
+
|
|
652
|
+
**Summary:** ${summary.total} total, ${summary.healthy} healthy, ${summary.warning} warning, ${summary.error} error`;
|
|
653
|
+
}
|
|
371
654
|
return {
|
|
372
|
-
content: [{ type: "text", text
|
|
655
|
+
content: [{ type: "text", text }]
|
|
373
656
|
};
|
|
374
657
|
}
|
|
375
658
|
);
|
|
@@ -386,6 +669,13 @@ function registerAnalyticsTools(server2, client2) {
|
|
|
386
669
|
from_date: z2.string().optional().describe("Start date for analytics range (ISO 8601)"),
|
|
387
670
|
to_date: z2.string().optional().describe("End date for analytics range (ISO 8601)")
|
|
388
671
|
},
|
|
672
|
+
{
|
|
673
|
+
title: "Get Analytics",
|
|
674
|
+
readOnlyHint: true,
|
|
675
|
+
destructiveHint: false,
|
|
676
|
+
idempotentHint: true,
|
|
677
|
+
openWorldHint: true
|
|
678
|
+
},
|
|
389
679
|
async (args) => {
|
|
390
680
|
const params = {};
|
|
391
681
|
if (args.platform) params.platform = args.platform;
|
|
@@ -408,6 +698,13 @@ function registerInboxTools(server2, client2) {
|
|
|
408
698
|
{
|
|
409
699
|
platform: z3.string().optional().describe("Filter by platform: twitter, instagram, linkedin, facebook")
|
|
410
700
|
},
|
|
701
|
+
{
|
|
702
|
+
title: "List Conversations",
|
|
703
|
+
readOnlyHint: true,
|
|
704
|
+
destructiveHint: false,
|
|
705
|
+
idempotentHint: true,
|
|
706
|
+
openWorldHint: true
|
|
707
|
+
},
|
|
411
708
|
async (args) => {
|
|
412
709
|
const params = {};
|
|
413
710
|
if (args.platform) params.platform = args.platform;
|
|
@@ -423,6 +720,13 @@ function registerInboxTools(server2, client2) {
|
|
|
423
720
|
{
|
|
424
721
|
conversation_id: z3.string().describe("The conversation ID")
|
|
425
722
|
},
|
|
723
|
+
{
|
|
724
|
+
title: "Get Conversation Messages",
|
|
725
|
+
readOnlyHint: true,
|
|
726
|
+
destructiveHint: false,
|
|
727
|
+
idempotentHint: true,
|
|
728
|
+
openWorldHint: true
|
|
729
|
+
},
|
|
426
730
|
async (args) => {
|
|
427
731
|
const result = await client2.getConversation(args.conversation_id);
|
|
428
732
|
return {
|
|
@@ -432,12 +736,29 @@ function registerInboxTools(server2, client2) {
|
|
|
432
736
|
);
|
|
433
737
|
server2.tool(
|
|
434
738
|
"reply_to_conversation",
|
|
435
|
-
"Send a reply message in a DM conversation.",
|
|
739
|
+
"Send a reply message in a DM conversation. Sends a message to an external platform on your behalf.",
|
|
436
740
|
{
|
|
437
741
|
conversation_id: z3.string().describe("The conversation ID to reply to"),
|
|
438
|
-
message: z3.string().describe("The reply message text")
|
|
742
|
+
message: z3.string().describe("The reply message text"),
|
|
743
|
+
confirmed: z3.boolean().optional().describe("Set to true to confirm and send. If false or missing, returns a preview for user approval.")
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
title: "Reply to Conversation",
|
|
747
|
+
readOnlyHint: false,
|
|
748
|
+
destructiveHint: false,
|
|
749
|
+
idempotentHint: false,
|
|
750
|
+
openWorldHint: true
|
|
439
751
|
},
|
|
440
752
|
async (args) => {
|
|
753
|
+
if (!args.confirmed) {
|
|
754
|
+
const preview = `## Reply Preview
|
|
755
|
+
|
|
756
|
+
**Conversation:** ${args.conversation_id}
|
|
757
|
+
**Message:** "${args.message}"
|
|
758
|
+
|
|
759
|
+
This will send a DM reply on the external platform. Call this tool again with confirmed=true to send.`;
|
|
760
|
+
return { content: [{ type: "text", text: preview }] };
|
|
761
|
+
}
|
|
441
762
|
const result = await client2.replyToConversation(
|
|
442
763
|
args.conversation_id,
|
|
443
764
|
args.message
|
|
@@ -453,6 +774,13 @@ function registerInboxTools(server2, client2) {
|
|
|
453
774
|
{
|
|
454
775
|
post_id: z3.string().optional().describe("Filter comments by post ID")
|
|
455
776
|
},
|
|
777
|
+
{
|
|
778
|
+
title: "List Comments",
|
|
779
|
+
readOnlyHint: true,
|
|
780
|
+
destructiveHint: false,
|
|
781
|
+
idempotentHint: true,
|
|
782
|
+
openWorldHint: true
|
|
783
|
+
},
|
|
456
784
|
async (args) => {
|
|
457
785
|
const params = {};
|
|
458
786
|
if (args.post_id) params.postId = args.post_id;
|
|
@@ -464,12 +792,29 @@ function registerInboxTools(server2, client2) {
|
|
|
464
792
|
);
|
|
465
793
|
server2.tool(
|
|
466
794
|
"reply_to_comment",
|
|
467
|
-
"Reply to a comment on one of your posts.",
|
|
795
|
+
"Reply to a comment on one of your posts. Sends a reply on the external platform.",
|
|
468
796
|
{
|
|
469
797
|
comment_id: z3.string().describe("The comment ID to reply to"),
|
|
470
|
-
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 Comment",
|
|
803
|
+
readOnlyHint: false,
|
|
804
|
+
destructiveHint: false,
|
|
805
|
+
idempotentHint: false,
|
|
806
|
+
openWorldHint: true
|
|
471
807
|
},
|
|
472
808
|
async (args) => {
|
|
809
|
+
if (!args.confirmed) {
|
|
810
|
+
const preview = `## Comment Reply Preview
|
|
811
|
+
|
|
812
|
+
**Comment ID:** ${args.comment_id}
|
|
813
|
+
**Reply:** "${args.message}"
|
|
814
|
+
|
|
815
|
+
This will post a public reply. Call this tool again with confirmed=true to send.`;
|
|
816
|
+
return { content: [{ type: "text", text: preview }] };
|
|
817
|
+
}
|
|
473
818
|
const result = await client2.replyToComment(
|
|
474
819
|
args.comment_id,
|
|
475
820
|
args.message
|
|
@@ -483,6 +828,13 @@ function registerInboxTools(server2, client2) {
|
|
|
483
828
|
"list_reviews",
|
|
484
829
|
"List reviews on your Facebook page.",
|
|
485
830
|
{},
|
|
831
|
+
{
|
|
832
|
+
title: "List Reviews",
|
|
833
|
+
readOnlyHint: true,
|
|
834
|
+
destructiveHint: false,
|
|
835
|
+
idempotentHint: true,
|
|
836
|
+
openWorldHint: true
|
|
837
|
+
},
|
|
486
838
|
async () => {
|
|
487
839
|
const result = await client2.listReviews();
|
|
488
840
|
return {
|
|
@@ -492,12 +844,29 @@ function registerInboxTools(server2, client2) {
|
|
|
492
844
|
);
|
|
493
845
|
server2.tool(
|
|
494
846
|
"reply_to_review",
|
|
495
|
-
"Reply to a review on your Facebook page.",
|
|
847
|
+
"Reply to a review on your Facebook page. Sends a public reply on the external platform.",
|
|
496
848
|
{
|
|
497
849
|
review_id: z3.string().describe("The review ID to reply to"),
|
|
498
|
-
message: z3.string().describe("The reply text")
|
|
850
|
+
message: z3.string().describe("The reply text"),
|
|
851
|
+
confirmed: z3.boolean().optional().describe("Set to true to confirm and send. If false or missing, returns a preview for user approval.")
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
title: "Reply to Review",
|
|
855
|
+
readOnlyHint: false,
|
|
856
|
+
destructiveHint: false,
|
|
857
|
+
idempotentHint: false,
|
|
858
|
+
openWorldHint: true
|
|
499
859
|
},
|
|
500
860
|
async (args) => {
|
|
861
|
+
if (!args.confirmed) {
|
|
862
|
+
const preview = `## Review Reply Preview
|
|
863
|
+
|
|
864
|
+
**Review ID:** ${args.review_id}
|
|
865
|
+
**Reply:** "${args.message}"
|
|
866
|
+
|
|
867
|
+
This will post a public reply to the review. Call this tool again with confirmed=true to send.`;
|
|
868
|
+
return { content: [{ type: "text", text: preview }] };
|
|
869
|
+
}
|
|
501
870
|
const result = await client2.replyToReview(
|
|
502
871
|
args.review_id,
|
|
503
872
|
args.message
|
|
@@ -519,9 +888,15 @@ function registerMediaTools(server2, client2) {
|
|
|
519
888
|
{
|
|
520
889
|
file_path: z4.string().describe("Absolute path to the file on the local filesystem")
|
|
521
890
|
},
|
|
891
|
+
{
|
|
892
|
+
title: "Upload Media File",
|
|
893
|
+
readOnlyHint: false,
|
|
894
|
+
destructiveHint: false,
|
|
895
|
+
idempotentHint: false,
|
|
896
|
+
openWorldHint: false
|
|
897
|
+
},
|
|
522
898
|
async (args) => {
|
|
523
|
-
const buffer = await readFile(args.file_path);
|
|
524
|
-
const base64 = buffer.toString("base64");
|
|
899
|
+
const buffer = Buffer.from(await readFile(args.file_path));
|
|
525
900
|
const filename = args.file_path.split("/").pop() ?? "upload";
|
|
526
901
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
527
902
|
const mimeMap = {
|
|
@@ -537,26 +912,57 @@ function registerMediaTools(server2, client2) {
|
|
|
537
912
|
pdf: "application/pdf"
|
|
538
913
|
};
|
|
539
914
|
const mimeType = mimeMap[ext] ?? "application/octet-stream";
|
|
540
|
-
const result = await client2.
|
|
915
|
+
const result = await client2.uploadMediaMultipart(filename, buffer, mimeType);
|
|
541
916
|
return {
|
|
542
917
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
543
918
|
};
|
|
544
919
|
}
|
|
545
920
|
);
|
|
546
921
|
server2.tool(
|
|
547
|
-
"
|
|
548
|
-
"Upload media from
|
|
922
|
+
"upload_from_url",
|
|
923
|
+
"Upload media from a public URL. The server fetches the image/video and uploads it to storage. Returns a CDN URL that can be used in posts. Supports JPEG, PNG, GIF, WebP, MP4, MOV, WebM up to 25MB.",
|
|
924
|
+
{
|
|
925
|
+
url: z4.string().url().describe("Public URL of the image or video to upload"),
|
|
926
|
+
filename: z4.string().optional().describe("Optional filename override (including extension)"),
|
|
927
|
+
folder: z4.string().optional().describe("Optional folder path within the customer's storage")
|
|
928
|
+
},
|
|
549
929
|
{
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
930
|
+
title: "Upload Media from URL",
|
|
931
|
+
readOnlyHint: false,
|
|
932
|
+
destructiveHint: false,
|
|
933
|
+
idempotentHint: false,
|
|
934
|
+
openWorldHint: false
|
|
553
935
|
},
|
|
554
936
|
async (args) => {
|
|
555
|
-
const result = await client2.
|
|
556
|
-
args.
|
|
557
|
-
args.
|
|
558
|
-
args.
|
|
559
|
-
);
|
|
937
|
+
const result = await client2.uploadFromUrl({
|
|
938
|
+
url: args.url,
|
|
939
|
+
filename: args.filename,
|
|
940
|
+
folder: args.folder
|
|
941
|
+
});
|
|
942
|
+
return {
|
|
943
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
);
|
|
947
|
+
server2.tool(
|
|
948
|
+
"get_upload_url",
|
|
949
|
+
"Get a pre-signed URL for direct client upload to storage. The URL is valid for 5 minutes. After uploading to the URL, the file will be available at the returned CDN URL.",
|
|
950
|
+
{
|
|
951
|
+
filename: z4.string().describe("The filename including extension (e.g. photo.jpg)"),
|
|
952
|
+
content_type: z4.string().describe("MIME type of the file (e.g. image/jpeg, video/mp4)")
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
title: "Get Upload URL",
|
|
956
|
+
readOnlyHint: true,
|
|
957
|
+
destructiveHint: false,
|
|
958
|
+
idempotentHint: false,
|
|
959
|
+
openWorldHint: false
|
|
960
|
+
},
|
|
961
|
+
async (args) => {
|
|
962
|
+
const result = await client2.getUploadUrl({
|
|
963
|
+
filename: args.filename,
|
|
964
|
+
content_type: args.content_type
|
|
965
|
+
});
|
|
560
966
|
return {
|
|
561
967
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
562
968
|
};
|
|
@@ -566,6 +972,13 @@ function registerMediaTools(server2, client2) {
|
|
|
566
972
|
"list_media",
|
|
567
973
|
"List all uploaded media files in your BuzzPoster media library.",
|
|
568
974
|
{},
|
|
975
|
+
{
|
|
976
|
+
title: "List Media Library",
|
|
977
|
+
readOnlyHint: true,
|
|
978
|
+
destructiveHint: false,
|
|
979
|
+
idempotentHint: true,
|
|
980
|
+
openWorldHint: false
|
|
981
|
+
},
|
|
569
982
|
async () => {
|
|
570
983
|
const result = await client2.listMedia();
|
|
571
984
|
return {
|
|
@@ -575,11 +988,27 @@ function registerMediaTools(server2, client2) {
|
|
|
575
988
|
);
|
|
576
989
|
server2.tool(
|
|
577
990
|
"delete_media",
|
|
578
|
-
"Delete a media file from your BuzzPoster media library.",
|
|
991
|
+
"Delete a media file from your BuzzPoster media library. This cannot be undone.",
|
|
992
|
+
{
|
|
993
|
+
key: z4.string().describe("The key/path of the media file to delete"),
|
|
994
|
+
confirmed: z4.boolean().optional().describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
995
|
+
},
|
|
579
996
|
{
|
|
580
|
-
|
|
997
|
+
title: "Delete Media File",
|
|
998
|
+
readOnlyHint: false,
|
|
999
|
+
destructiveHint: true,
|
|
1000
|
+
idempotentHint: true,
|
|
1001
|
+
openWorldHint: false
|
|
581
1002
|
},
|
|
582
1003
|
async (args) => {
|
|
1004
|
+
if (!args.confirmed) {
|
|
1005
|
+
const preview = `## Delete Media Confirmation
|
|
1006
|
+
|
|
1007
|
+
**File:** ${args.key}
|
|
1008
|
+
|
|
1009
|
+
This will permanently delete this media file. Call this tool again with confirmed=true to proceed.`;
|
|
1010
|
+
return { content: [{ type: "text", text: preview }] };
|
|
1011
|
+
}
|
|
583
1012
|
await client2.deleteMedia(args.key);
|
|
584
1013
|
return {
|
|
585
1014
|
content: [{ type: "text", text: "Media deleted successfully." }]
|
|
@@ -598,6 +1027,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
598
1027
|
page: z5.string().optional().describe("Page number for pagination"),
|
|
599
1028
|
per_page: z5.string().optional().describe("Number of subscribers per page")
|
|
600
1029
|
},
|
|
1030
|
+
{
|
|
1031
|
+
title: "List Subscribers",
|
|
1032
|
+
readOnlyHint: true,
|
|
1033
|
+
destructiveHint: false,
|
|
1034
|
+
idempotentHint: true,
|
|
1035
|
+
openWorldHint: true
|
|
1036
|
+
},
|
|
601
1037
|
async (args) => {
|
|
602
1038
|
const params = {};
|
|
603
1039
|
if (args.page) params.page = args.page;
|
|
@@ -616,6 +1052,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
616
1052
|
first_name: z5.string().optional().describe("Subscriber's first name"),
|
|
617
1053
|
last_name: z5.string().optional().describe("Subscriber's last name")
|
|
618
1054
|
},
|
|
1055
|
+
{
|
|
1056
|
+
title: "Add Subscriber",
|
|
1057
|
+
readOnlyHint: false,
|
|
1058
|
+
destructiveHint: false,
|
|
1059
|
+
idempotentHint: false,
|
|
1060
|
+
openWorldHint: true
|
|
1061
|
+
},
|
|
619
1062
|
async (args) => {
|
|
620
1063
|
const result = await client2.addSubscriber({
|
|
621
1064
|
email: args.email,
|
|
@@ -635,6 +1078,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
635
1078
|
content: z5.string().describe("HTML content of the newsletter"),
|
|
636
1079
|
preview_text: z5.string().optional().describe("Preview text shown in email clients")
|
|
637
1080
|
},
|
|
1081
|
+
{
|
|
1082
|
+
title: "Create Newsletter Draft",
|
|
1083
|
+
readOnlyHint: false,
|
|
1084
|
+
destructiveHint: false,
|
|
1085
|
+
idempotentHint: false,
|
|
1086
|
+
openWorldHint: true
|
|
1087
|
+
},
|
|
638
1088
|
async (args) => {
|
|
639
1089
|
const result = await client2.createBroadcast({
|
|
640
1090
|
subject: args.subject,
|
|
@@ -655,6 +1105,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
655
1105
|
content: z5.string().optional().describe("Updated HTML content"),
|
|
656
1106
|
preview_text: z5.string().optional().describe("Updated preview text")
|
|
657
1107
|
},
|
|
1108
|
+
{
|
|
1109
|
+
title: "Update Newsletter Draft",
|
|
1110
|
+
readOnlyHint: false,
|
|
1111
|
+
destructiveHint: false,
|
|
1112
|
+
idempotentHint: true,
|
|
1113
|
+
openWorldHint: true
|
|
1114
|
+
},
|
|
658
1115
|
async (args) => {
|
|
659
1116
|
const data = {};
|
|
660
1117
|
if (args.subject) data.subject = args.subject;
|
|
@@ -668,11 +1125,31 @@ function registerNewsletterTools(server2, client2) {
|
|
|
668
1125
|
);
|
|
669
1126
|
server2.tool(
|
|
670
1127
|
"send_newsletter",
|
|
671
|
-
"Send a newsletter/broadcast to subscribers. This action cannot be undone.",
|
|
1128
|
+
"Send a newsletter/broadcast to subscribers. This action cannot be undone. Requires confirmation before sending.",
|
|
1129
|
+
{
|
|
1130
|
+
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1131
|
+
confirmed: z5.boolean().optional().describe(
|
|
1132
|
+
"Set to true to confirm and send. If false or missing, returns a confirmation prompt."
|
|
1133
|
+
)
|
|
1134
|
+
},
|
|
672
1135
|
{
|
|
673
|
-
|
|
1136
|
+
title: "Send Newsletter",
|
|
1137
|
+
readOnlyHint: false,
|
|
1138
|
+
destructiveHint: false,
|
|
1139
|
+
idempotentHint: false,
|
|
1140
|
+
openWorldHint: true
|
|
674
1141
|
},
|
|
675
1142
|
async (args) => {
|
|
1143
|
+
if (!args.confirmed) {
|
|
1144
|
+
const preview = `## Send Newsletter Confirmation
|
|
1145
|
+
|
|
1146
|
+
**Broadcast ID:** ${args.broadcast_id}
|
|
1147
|
+
|
|
1148
|
+
This will send the newsletter to your subscriber list. **This action cannot be undone.**
|
|
1149
|
+
|
|
1150
|
+
Call this tool again with confirmed=true to send.`;
|
|
1151
|
+
return { content: [{ type: "text", text: preview }] };
|
|
1152
|
+
}
|
|
676
1153
|
await client2.sendBroadcast(args.broadcast_id);
|
|
677
1154
|
return {
|
|
678
1155
|
content: [{ type: "text", text: "Newsletter sent successfully." }]
|
|
@@ -685,6 +1162,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
685
1162
|
{
|
|
686
1163
|
page: z5.string().optional().describe("Page number for pagination")
|
|
687
1164
|
},
|
|
1165
|
+
{
|
|
1166
|
+
title: "List Newsletters",
|
|
1167
|
+
readOnlyHint: true,
|
|
1168
|
+
destructiveHint: false,
|
|
1169
|
+
idempotentHint: true,
|
|
1170
|
+
openWorldHint: true
|
|
1171
|
+
},
|
|
688
1172
|
async (args) => {
|
|
689
1173
|
const params = {};
|
|
690
1174
|
if (args.page) params.page = args.page;
|
|
@@ -698,6 +1182,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
698
1182
|
"list_tags",
|
|
699
1183
|
"List all subscriber tags from your email service provider.",
|
|
700
1184
|
{},
|
|
1185
|
+
{
|
|
1186
|
+
title: "List Subscriber Tags",
|
|
1187
|
+
readOnlyHint: true,
|
|
1188
|
+
destructiveHint: false,
|
|
1189
|
+
idempotentHint: true,
|
|
1190
|
+
openWorldHint: true
|
|
1191
|
+
},
|
|
701
1192
|
async () => {
|
|
702
1193
|
const result = await client2.listTags();
|
|
703
1194
|
return {
|
|
@@ -709,6 +1200,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
709
1200
|
"list_sequences",
|
|
710
1201
|
"List all email sequences/automations from your email service provider.",
|
|
711
1202
|
{},
|
|
1203
|
+
{
|
|
1204
|
+
title: "List Email Sequences",
|
|
1205
|
+
readOnlyHint: true,
|
|
1206
|
+
destructiveHint: false,
|
|
1207
|
+
idempotentHint: true,
|
|
1208
|
+
openWorldHint: true
|
|
1209
|
+
},
|
|
712
1210
|
async () => {
|
|
713
1211
|
const result = await client2.listSequences();
|
|
714
1212
|
return {
|
|
@@ -720,6 +1218,13 @@ function registerNewsletterTools(server2, client2) {
|
|
|
720
1218
|
"list_forms",
|
|
721
1219
|
"List all signup forms from your email service provider.",
|
|
722
1220
|
{},
|
|
1221
|
+
{
|
|
1222
|
+
title: "List Signup Forms",
|
|
1223
|
+
readOnlyHint: true,
|
|
1224
|
+
destructiveHint: false,
|
|
1225
|
+
idempotentHint: true,
|
|
1226
|
+
openWorldHint: true
|
|
1227
|
+
},
|
|
723
1228
|
async () => {
|
|
724
1229
|
const result = await client2.listForms();
|
|
725
1230
|
return {
|
|
@@ -739,6 +1244,13 @@ function registerRssTools(server2, client2) {
|
|
|
739
1244
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
740
1245
|
limit: z6.number().optional().describe("Maximum number of entries to return (default 10, max 100)")
|
|
741
1246
|
},
|
|
1247
|
+
{
|
|
1248
|
+
title: "Fetch RSS Feed",
|
|
1249
|
+
readOnlyHint: true,
|
|
1250
|
+
destructiveHint: false,
|
|
1251
|
+
idempotentHint: true,
|
|
1252
|
+
openWorldHint: true
|
|
1253
|
+
},
|
|
742
1254
|
async (args) => {
|
|
743
1255
|
const result = await client2.fetchFeed(args.url, args.limit);
|
|
744
1256
|
return {
|
|
@@ -752,6 +1264,13 @@ function registerRssTools(server2, client2) {
|
|
|
752
1264
|
{
|
|
753
1265
|
url: z6.string().describe("The article URL to extract content from")
|
|
754
1266
|
},
|
|
1267
|
+
{
|
|
1268
|
+
title: "Fetch Article Content",
|
|
1269
|
+
readOnlyHint: true,
|
|
1270
|
+
destructiveHint: false,
|
|
1271
|
+
idempotentHint: true,
|
|
1272
|
+
openWorldHint: true
|
|
1273
|
+
},
|
|
755
1274
|
async (args) => {
|
|
756
1275
|
const result = await client2.fetchArticle(args.url);
|
|
757
1276
|
return {
|
|
@@ -767,6 +1286,13 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
767
1286
|
"get_account",
|
|
768
1287
|
"Get your BuzzPoster account details including name, email, subscription status, ESP configuration, and connected platforms.",
|
|
769
1288
|
{},
|
|
1289
|
+
{
|
|
1290
|
+
title: "Get Account Details",
|
|
1291
|
+
readOnlyHint: true,
|
|
1292
|
+
destructiveHint: false,
|
|
1293
|
+
idempotentHint: true,
|
|
1294
|
+
openWorldHint: false
|
|
1295
|
+
},
|
|
770
1296
|
async () => {
|
|
771
1297
|
const result = await client2.getAccount();
|
|
772
1298
|
return {
|
|
@@ -776,6 +1302,894 @@ function registerAccountInfoTool(server2, client2) {
|
|
|
776
1302
|
);
|
|
777
1303
|
}
|
|
778
1304
|
|
|
1305
|
+
// src/tools/brand-voice.ts
|
|
1306
|
+
function registerBrandVoiceTools(server2, client2) {
|
|
1307
|
+
server2.tool(
|
|
1308
|
+
"get_brand_voice",
|
|
1309
|
+
"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.",
|
|
1310
|
+
{},
|
|
1311
|
+
{
|
|
1312
|
+
title: "Get Brand Voice",
|
|
1313
|
+
readOnlyHint: true,
|
|
1314
|
+
destructiveHint: false,
|
|
1315
|
+
idempotentHint: true,
|
|
1316
|
+
openWorldHint: false
|
|
1317
|
+
},
|
|
1318
|
+
async () => {
|
|
1319
|
+
try {
|
|
1320
|
+
const voice = await client2.getBrandVoice();
|
|
1321
|
+
const lines = [];
|
|
1322
|
+
lines.push(`## Brand Voice: ${voice.name || "My Brand"}`);
|
|
1323
|
+
lines.push("");
|
|
1324
|
+
if (voice.description) {
|
|
1325
|
+
lines.push("### Voice Description");
|
|
1326
|
+
lines.push(voice.description);
|
|
1327
|
+
lines.push("");
|
|
1328
|
+
}
|
|
1329
|
+
if (voice.dos && voice.dos.length > 0) {
|
|
1330
|
+
lines.push("### Do's");
|
|
1331
|
+
for (const rule of voice.dos) {
|
|
1332
|
+
lines.push(`- ${rule}`);
|
|
1333
|
+
}
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
}
|
|
1336
|
+
if (voice.donts && voice.donts.length > 0) {
|
|
1337
|
+
lines.push("### Don'ts");
|
|
1338
|
+
for (const rule of voice.donts) {
|
|
1339
|
+
lines.push(`- ${rule}`);
|
|
1340
|
+
}
|
|
1341
|
+
lines.push("");
|
|
1342
|
+
}
|
|
1343
|
+
if (voice.platformRules && Object.keys(voice.platformRules).length > 0) {
|
|
1344
|
+
lines.push("### Platform-Specific Rules");
|
|
1345
|
+
for (const [platform, rules] of Object.entries(voice.platformRules)) {
|
|
1346
|
+
lines.push(`**${platform}:** ${rules}`);
|
|
1347
|
+
}
|
|
1348
|
+
lines.push("");
|
|
1349
|
+
}
|
|
1350
|
+
if (voice.examplePosts && voice.examplePosts.length > 0) {
|
|
1351
|
+
lines.push("### Example Posts");
|
|
1352
|
+
voice.examplePosts.forEach((post, i) => {
|
|
1353
|
+
lines.push(`${i + 1}. ${post}`);
|
|
1354
|
+
});
|
|
1355
|
+
lines.push("");
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1359
|
+
};
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1362
|
+
if (message.includes("404") || message.includes("No brand voice")) {
|
|
1363
|
+
return {
|
|
1364
|
+
content: [
|
|
1365
|
+
{
|
|
1366
|
+
type: "text",
|
|
1367
|
+
text: "No brand voice has been configured yet. The customer can set one up at their BuzzPoster dashboard."
|
|
1368
|
+
}
|
|
1369
|
+
]
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/tools/knowledge.ts
|
|
1379
|
+
import { z as z7 } from "zod";
|
|
1380
|
+
function registerKnowledgeTools(server2, client2) {
|
|
1381
|
+
server2.tool(
|
|
1382
|
+
"get_knowledge_base",
|
|
1383
|
+
"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.",
|
|
1384
|
+
{},
|
|
1385
|
+
{
|
|
1386
|
+
title: "Get Knowledge Base",
|
|
1387
|
+
readOnlyHint: true,
|
|
1388
|
+
destructiveHint: false,
|
|
1389
|
+
idempotentHint: true,
|
|
1390
|
+
openWorldHint: false
|
|
1391
|
+
},
|
|
1392
|
+
async () => {
|
|
1393
|
+
try {
|
|
1394
|
+
const data = await client2.listKnowledge();
|
|
1395
|
+
const items = data.items ?? [];
|
|
1396
|
+
if (items.length === 0) {
|
|
1397
|
+
return {
|
|
1398
|
+
content: [
|
|
1399
|
+
{
|
|
1400
|
+
type: "text",
|
|
1401
|
+
text: "The knowledge base is empty. The customer can add reference material at their BuzzPoster dashboard."
|
|
1402
|
+
}
|
|
1403
|
+
]
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
const totalChars = items.reduce(
|
|
1407
|
+
(sum, item) => sum + item.content.length,
|
|
1408
|
+
0
|
|
1409
|
+
);
|
|
1410
|
+
const shouldTruncate = totalChars > 1e4;
|
|
1411
|
+
const lines = [];
|
|
1412
|
+
lines.push(`## Knowledge Base (${items.length} items)`);
|
|
1413
|
+
lines.push("");
|
|
1414
|
+
for (const item of items) {
|
|
1415
|
+
lines.push(`### ${item.title}`);
|
|
1416
|
+
if (item.tags && item.tags.length > 0) {
|
|
1417
|
+
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1418
|
+
}
|
|
1419
|
+
if (shouldTruncate) {
|
|
1420
|
+
lines.push(item.content.slice(0, 500) + " [truncated]");
|
|
1421
|
+
} else {
|
|
1422
|
+
lines.push(item.content);
|
|
1423
|
+
}
|
|
1424
|
+
lines.push("");
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1428
|
+
};
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1431
|
+
throw new Error(`Failed to get knowledge base: ${message}`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
server2.tool(
|
|
1436
|
+
"search_knowledge",
|
|
1437
|
+
"Search the customer's knowledge base by tag or keyword. Use this to find specific reference material when writing about a topic.",
|
|
1438
|
+
{
|
|
1439
|
+
query: z7.string().describe(
|
|
1440
|
+
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
1441
|
+
)
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
title: "Search Knowledge Base",
|
|
1445
|
+
readOnlyHint: true,
|
|
1446
|
+
destructiveHint: false,
|
|
1447
|
+
idempotentHint: true,
|
|
1448
|
+
openWorldHint: false
|
|
1449
|
+
},
|
|
1450
|
+
async ({ query }) => {
|
|
1451
|
+
try {
|
|
1452
|
+
const data = await client2.listKnowledge({ tag: query });
|
|
1453
|
+
const items = data.items ?? [];
|
|
1454
|
+
if (items.length === 0) {
|
|
1455
|
+
return {
|
|
1456
|
+
content: [
|
|
1457
|
+
{
|
|
1458
|
+
type: "text",
|
|
1459
|
+
text: `No knowledge base items found matching "${query}".`
|
|
1460
|
+
}
|
|
1461
|
+
]
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
const lines = [];
|
|
1465
|
+
lines.push(
|
|
1466
|
+
`## Knowledge Base Results for "${query}" (${items.length} items)`
|
|
1467
|
+
);
|
|
1468
|
+
lines.push("");
|
|
1469
|
+
for (const item of items) {
|
|
1470
|
+
lines.push(`### ${item.title}`);
|
|
1471
|
+
if (item.tags && item.tags.length > 0) {
|
|
1472
|
+
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1473
|
+
}
|
|
1474
|
+
lines.push(item.content);
|
|
1475
|
+
lines.push("");
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1479
|
+
};
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1482
|
+
throw new Error(`Failed to search knowledge base: ${message}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
);
|
|
1486
|
+
server2.tool(
|
|
1487
|
+
"add_knowledge",
|
|
1488
|
+
"Add a new item to the customer's knowledge base. Use this to save useful information the customer shares during conversation.",
|
|
1489
|
+
{
|
|
1490
|
+
title: z7.string().describe("Title for the knowledge item"),
|
|
1491
|
+
content: z7.string().describe("The content/text to save"),
|
|
1492
|
+
tags: z7.array(z7.string()).optional().describe("Optional tags for categorization")
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
title: "Add Knowledge Item",
|
|
1496
|
+
readOnlyHint: false,
|
|
1497
|
+
destructiveHint: false,
|
|
1498
|
+
idempotentHint: false,
|
|
1499
|
+
openWorldHint: false
|
|
1500
|
+
},
|
|
1501
|
+
async ({ title, content, tags }) => {
|
|
1502
|
+
try {
|
|
1503
|
+
await client2.createKnowledge({
|
|
1504
|
+
title,
|
|
1505
|
+
content,
|
|
1506
|
+
sourceType: "text",
|
|
1507
|
+
tags: tags ?? []
|
|
1508
|
+
});
|
|
1509
|
+
return {
|
|
1510
|
+
content: [
|
|
1511
|
+
{
|
|
1512
|
+
type: "text",
|
|
1513
|
+
text: `Added "${title}" to the knowledge base.`
|
|
1514
|
+
}
|
|
1515
|
+
]
|
|
1516
|
+
};
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1519
|
+
throw new Error(`Failed to add knowledge item: ${message}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/tools/audience.ts
|
|
1526
|
+
import { z as z8 } from "zod";
|
|
1527
|
+
function registerAudienceTools(server2, client2) {
|
|
1528
|
+
server2.tool(
|
|
1529
|
+
"get_audience",
|
|
1530
|
+
"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.",
|
|
1531
|
+
{
|
|
1532
|
+
audienceId: z8.string().optional().describe("Specific audience profile ID. If omitted, returns the default audience.")
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
title: "Get Audience Profile",
|
|
1536
|
+
readOnlyHint: true,
|
|
1537
|
+
destructiveHint: false,
|
|
1538
|
+
idempotentHint: true,
|
|
1539
|
+
openWorldHint: false
|
|
1540
|
+
},
|
|
1541
|
+
async ({ audienceId }) => {
|
|
1542
|
+
try {
|
|
1543
|
+
const audience = audienceId ? await client2.getAudience(audienceId) : await client2.getDefaultAudience();
|
|
1544
|
+
const lines = [];
|
|
1545
|
+
lines.push(`## Target Audience: ${audience.name}`);
|
|
1546
|
+
lines.push("");
|
|
1547
|
+
if (audience.description) {
|
|
1548
|
+
lines.push("### Description");
|
|
1549
|
+
lines.push(audience.description);
|
|
1550
|
+
lines.push("");
|
|
1551
|
+
}
|
|
1552
|
+
if (audience.demographics) {
|
|
1553
|
+
lines.push("### Demographics");
|
|
1554
|
+
lines.push(audience.demographics);
|
|
1555
|
+
lines.push("");
|
|
1556
|
+
}
|
|
1557
|
+
if (audience.painPoints && audience.painPoints.length > 0) {
|
|
1558
|
+
lines.push("### Pain Points");
|
|
1559
|
+
for (const point of audience.painPoints) {
|
|
1560
|
+
lines.push(`- ${point}`);
|
|
1561
|
+
}
|
|
1562
|
+
lines.push("");
|
|
1563
|
+
}
|
|
1564
|
+
if (audience.motivations && audience.motivations.length > 0) {
|
|
1565
|
+
lines.push("### Motivations");
|
|
1566
|
+
for (const motivation of audience.motivations) {
|
|
1567
|
+
lines.push(`- ${motivation}`);
|
|
1568
|
+
}
|
|
1569
|
+
lines.push("");
|
|
1570
|
+
}
|
|
1571
|
+
if (audience.platforms && audience.platforms.length > 0) {
|
|
1572
|
+
lines.push("### Active Platforms");
|
|
1573
|
+
lines.push(audience.platforms.join(", "));
|
|
1574
|
+
lines.push("");
|
|
1575
|
+
}
|
|
1576
|
+
if (audience.toneNotes) {
|
|
1577
|
+
lines.push("### Tone Notes");
|
|
1578
|
+
lines.push(audience.toneNotes);
|
|
1579
|
+
lines.push("");
|
|
1580
|
+
}
|
|
1581
|
+
if (audience.contentPreferences) {
|
|
1582
|
+
lines.push("### Content Preferences");
|
|
1583
|
+
lines.push(audience.contentPreferences);
|
|
1584
|
+
lines.push("");
|
|
1585
|
+
}
|
|
1586
|
+
return {
|
|
1587
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1588
|
+
};
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1591
|
+
if (message.includes("404") || message.includes("No audience")) {
|
|
1592
|
+
return {
|
|
1593
|
+
content: [
|
|
1594
|
+
{
|
|
1595
|
+
type: "text",
|
|
1596
|
+
text: "No audience profile has been configured yet. The customer can set one up at their BuzzPoster dashboard under Audiences."
|
|
1597
|
+
}
|
|
1598
|
+
]
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
throw error;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// src/tools/newsletter-template.ts
|
|
1608
|
+
import { z as z9 } from "zod";
|
|
1609
|
+
function registerNewsletterTemplateTools(server2, client2) {
|
|
1610
|
+
server2.tool(
|
|
1611
|
+
"get_newsletter_template",
|
|
1612
|
+
"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.",
|
|
1613
|
+
{
|
|
1614
|
+
templateId: z9.string().optional().describe(
|
|
1615
|
+
"Specific template ID. If omitted, returns the default template."
|
|
1616
|
+
)
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
title: "Get Newsletter Template",
|
|
1620
|
+
readOnlyHint: true,
|
|
1621
|
+
destructiveHint: false,
|
|
1622
|
+
idempotentHint: true,
|
|
1623
|
+
openWorldHint: false
|
|
1624
|
+
},
|
|
1625
|
+
async ({ templateId }) => {
|
|
1626
|
+
try {
|
|
1627
|
+
const template = await client2.getTemplate(templateId);
|
|
1628
|
+
const lines = [];
|
|
1629
|
+
lines.push(`## Newsletter Template: ${template.name}`);
|
|
1630
|
+
if (template.description) lines.push(template.description);
|
|
1631
|
+
lines.push("");
|
|
1632
|
+
if (template.audienceId)
|
|
1633
|
+
lines.push(`Audience ID: ${template.audienceId}`);
|
|
1634
|
+
if (template.sendCadence)
|
|
1635
|
+
lines.push(`Cadence: ${template.sendCadence}`);
|
|
1636
|
+
if (template.subjectPattern)
|
|
1637
|
+
lines.push(`Subject pattern: ${template.subjectPattern}`);
|
|
1638
|
+
if (template.previewTextPattern)
|
|
1639
|
+
lines.push(`Preview text pattern: ${template.previewTextPattern}`);
|
|
1640
|
+
lines.push("");
|
|
1641
|
+
if (template.sections && template.sections.length > 0) {
|
|
1642
|
+
lines.push("### Sections (in order):");
|
|
1643
|
+
lines.push("");
|
|
1644
|
+
for (let i = 0; i < template.sections.length; i++) {
|
|
1645
|
+
const s = template.sections[i];
|
|
1646
|
+
lines.push(`**${i + 1}. ${s.label}** (${s.type})`);
|
|
1647
|
+
if (s.instructions)
|
|
1648
|
+
lines.push(`Instructions: ${s.instructions}`);
|
|
1649
|
+
if (s.word_count)
|
|
1650
|
+
lines.push(
|
|
1651
|
+
`Word count: ${s.word_count.min}-${s.word_count.max} words`
|
|
1652
|
+
);
|
|
1653
|
+
if (s.tone_override) lines.push(`Tone: ${s.tone_override}`);
|
|
1654
|
+
if (s.content_source) {
|
|
1655
|
+
const src = s.content_source;
|
|
1656
|
+
if (src.type === "rss" && src.feed_urls?.length)
|
|
1657
|
+
lines.push(`Content source: RSS - ${src.feed_urls.join(", ")}`);
|
|
1658
|
+
else if (src.type === "knowledge" && src.knowledge_item_ids?.length)
|
|
1659
|
+
lines.push(
|
|
1660
|
+
`Content source: Knowledge items ${src.knowledge_item_ids.join(", ")}`
|
|
1661
|
+
);
|
|
1662
|
+
else lines.push(`Content source: ${src.type}`);
|
|
1663
|
+
}
|
|
1664
|
+
if (s.count) lines.push(`Count: ${s.count} items`);
|
|
1665
|
+
lines.push(
|
|
1666
|
+
`Required: ${s.required !== false ? "yes" : "no"}`
|
|
1667
|
+
);
|
|
1668
|
+
if (s.type === "cta") {
|
|
1669
|
+
if (s.button_text) lines.push(`Button text: ${s.button_text}`);
|
|
1670
|
+
if (s.button_url) lines.push(`Button URL: ${s.button_url}`);
|
|
1671
|
+
}
|
|
1672
|
+
if (s.type === "sponsor") {
|
|
1673
|
+
if (s.sponsor_name)
|
|
1674
|
+
lines.push(`Sponsor: ${s.sponsor_name}`);
|
|
1675
|
+
if (s.talking_points)
|
|
1676
|
+
lines.push(`Talking points: ${s.talking_points}`);
|
|
1677
|
+
}
|
|
1678
|
+
if (s.type === "header") {
|
|
1679
|
+
if (s.tagline) lines.push(`Tagline: ${s.tagline}`);
|
|
1680
|
+
}
|
|
1681
|
+
if (s.type === "signoff") {
|
|
1682
|
+
if (s.author_name)
|
|
1683
|
+
lines.push(`Author: ${s.author_name}`);
|
|
1684
|
+
if (s.author_title)
|
|
1685
|
+
lines.push(`Title: ${s.author_title}`);
|
|
1686
|
+
}
|
|
1687
|
+
lines.push("");
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
if (template.style) {
|
|
1691
|
+
lines.push("### Style Guide:");
|
|
1692
|
+
const st = template.style;
|
|
1693
|
+
if (st.primary_color) lines.push(`Primary color: ${st.primary_color}`);
|
|
1694
|
+
if (st.accent_color) lines.push(`Accent color: ${st.accent_color}`);
|
|
1695
|
+
if (st.font_family) lines.push(`Font: ${st.font_family}`);
|
|
1696
|
+
if (st.heading_font) lines.push(`Heading font: ${st.heading_font}`);
|
|
1697
|
+
if (st.content_width)
|
|
1698
|
+
lines.push(`Content width: ${st.content_width}px`);
|
|
1699
|
+
if (st.text_color) lines.push(`Text color: ${st.text_color}`);
|
|
1700
|
+
if (st.link_color) lines.push(`Link color: ${st.link_color}`);
|
|
1701
|
+
if (st.background_color)
|
|
1702
|
+
lines.push(`Background: ${st.background_color}`);
|
|
1703
|
+
if (st.button_style)
|
|
1704
|
+
lines.push(
|
|
1705
|
+
`Button style: ${st.button_style.color} text on ${st.button_style.text_color}, ${st.button_style.shape}`
|
|
1706
|
+
);
|
|
1707
|
+
lines.push("");
|
|
1708
|
+
lines.push(
|
|
1709
|
+
"NOTE: Apply these style values as inline CSS when generating the newsletter HTML."
|
|
1710
|
+
);
|
|
1711
|
+
lines.push("");
|
|
1712
|
+
}
|
|
1713
|
+
if (template.rssFeedUrls && template.rssFeedUrls.length > 0) {
|
|
1714
|
+
lines.push("### Linked RSS Feeds:");
|
|
1715
|
+
for (const url of template.rssFeedUrls) {
|
|
1716
|
+
lines.push(`- ${url}`);
|
|
1717
|
+
}
|
|
1718
|
+
lines.push("");
|
|
1719
|
+
}
|
|
1720
|
+
if (template.knowledgeItemIds && template.knowledgeItemIds.length > 0) {
|
|
1721
|
+
lines.push("### Reference Material:");
|
|
1722
|
+
lines.push(
|
|
1723
|
+
`Knowledge item IDs: ${template.knowledgeItemIds.join(", ")}`
|
|
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
|
+
if (message.includes("404") || message.includes("No newsletter templates")) {
|
|
1733
|
+
return {
|
|
1734
|
+
content: [
|
|
1735
|
+
{
|
|
1736
|
+
type: "text",
|
|
1737
|
+
text: "No newsletter template has been configured yet. The customer can set one up at their BuzzPoster dashboard under Templates."
|
|
1738
|
+
}
|
|
1739
|
+
]
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
throw error;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
);
|
|
1746
|
+
server2.tool(
|
|
1747
|
+
"get_past_newsletters",
|
|
1748
|
+
"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.",
|
|
1749
|
+
{
|
|
1750
|
+
limit: z9.number().optional().describe("Number of past newsletters to retrieve. Default 5, max 50."),
|
|
1751
|
+
templateId: z9.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
title: "Get Past Newsletters",
|
|
1755
|
+
readOnlyHint: true,
|
|
1756
|
+
destructiveHint: false,
|
|
1757
|
+
idempotentHint: true,
|
|
1758
|
+
openWorldHint: false
|
|
1759
|
+
},
|
|
1760
|
+
async ({ limit, templateId }) => {
|
|
1761
|
+
try {
|
|
1762
|
+
const params = {};
|
|
1763
|
+
if (limit) params.limit = String(Math.min(limit, 50));
|
|
1764
|
+
else params.limit = "5";
|
|
1765
|
+
if (templateId) params.template_id = templateId;
|
|
1766
|
+
const newsletters = await client2.listNewsletterArchive(
|
|
1767
|
+
params
|
|
1768
|
+
);
|
|
1769
|
+
if (!newsletters || newsletters.length === 0) {
|
|
1770
|
+
return {
|
|
1771
|
+
content: [
|
|
1772
|
+
{
|
|
1773
|
+
type: "text",
|
|
1774
|
+
text: "No past newsletters found. This will be the first edition!"
|
|
1775
|
+
}
|
|
1776
|
+
]
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
const lines = [];
|
|
1780
|
+
lines.push(
|
|
1781
|
+
`## Past Newsletters (${newsletters.length} most recent)`
|
|
1782
|
+
);
|
|
1783
|
+
lines.push("");
|
|
1784
|
+
for (const nl of newsletters) {
|
|
1785
|
+
lines.push(`### ${nl.subject} -- ${nl.sentAt || nl.createdAt}`);
|
|
1786
|
+
if (nl.notes) lines.push(`Notes: ${nl.notes}`);
|
|
1787
|
+
if (nl.metrics) {
|
|
1788
|
+
const m = nl.metrics;
|
|
1789
|
+
const parts = [];
|
|
1790
|
+
if (m.open_rate) parts.push(`Open rate: ${m.open_rate}`);
|
|
1791
|
+
if (m.click_rate) parts.push(`Click rate: ${m.click_rate}`);
|
|
1792
|
+
if (parts.length) lines.push(`Metrics: ${parts.join(", ")}`);
|
|
1793
|
+
}
|
|
1794
|
+
lines.push("---");
|
|
1795
|
+
const textContent = nl.contentHtml ? nl.contentHtml.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 500) : "(no content)";
|
|
1796
|
+
lines.push(textContent);
|
|
1797
|
+
lines.push(
|
|
1798
|
+
`[Full content available via get_past_newsletter tool with ID: ${nl.id}]`
|
|
1799
|
+
);
|
|
1800
|
+
lines.push("");
|
|
1801
|
+
}
|
|
1802
|
+
return {
|
|
1803
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1804
|
+
};
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1807
|
+
throw new Error(`Failed to fetch past newsletters: ${message}`);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
);
|
|
1811
|
+
server2.tool(
|
|
1812
|
+
"get_past_newsletter",
|
|
1813
|
+
"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.",
|
|
1814
|
+
{
|
|
1815
|
+
newsletterId: z9.string().describe("The ID of the archived newsletter to retrieve.")
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
title: "Get Past Newsletter Detail",
|
|
1819
|
+
readOnlyHint: true,
|
|
1820
|
+
destructiveHint: false,
|
|
1821
|
+
idempotentHint: true,
|
|
1822
|
+
openWorldHint: false
|
|
1823
|
+
},
|
|
1824
|
+
async ({ newsletterId }) => {
|
|
1825
|
+
try {
|
|
1826
|
+
const newsletter = await client2.getArchivedNewsletter(
|
|
1827
|
+
newsletterId
|
|
1828
|
+
);
|
|
1829
|
+
const lines = [];
|
|
1830
|
+
lines.push(`## ${newsletter.subject}`);
|
|
1831
|
+
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
1832
|
+
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
1833
|
+
lines.push("");
|
|
1834
|
+
lines.push("### Full HTML Content:");
|
|
1835
|
+
lines.push(newsletter.contentHtml);
|
|
1836
|
+
return {
|
|
1837
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1838
|
+
};
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1841
|
+
if (message.includes("404")) {
|
|
1842
|
+
return {
|
|
1843
|
+
content: [
|
|
1844
|
+
{
|
|
1845
|
+
type: "text",
|
|
1846
|
+
text: "Newsletter not found. Check the ID and try again."
|
|
1847
|
+
}
|
|
1848
|
+
]
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
throw error;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
server2.tool(
|
|
1856
|
+
"save_newsletter",
|
|
1857
|
+
"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.",
|
|
1858
|
+
{
|
|
1859
|
+
subject: z9.string().describe("The newsletter subject line."),
|
|
1860
|
+
contentHtml: z9.string().describe("The full HTML content of the newsletter."),
|
|
1861
|
+
templateId: z9.string().optional().describe("The template ID used to generate this newsletter."),
|
|
1862
|
+
notes: z9.string().optional().describe(
|
|
1863
|
+
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
1864
|
+
)
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
title: "Save Newsletter to Archive",
|
|
1868
|
+
readOnlyHint: false,
|
|
1869
|
+
destructiveHint: false,
|
|
1870
|
+
idempotentHint: false,
|
|
1871
|
+
openWorldHint: false
|
|
1872
|
+
},
|
|
1873
|
+
async ({ subject, contentHtml, templateId, notes }) => {
|
|
1874
|
+
try {
|
|
1875
|
+
const data = {
|
|
1876
|
+
subject,
|
|
1877
|
+
contentHtml
|
|
1878
|
+
};
|
|
1879
|
+
if (templateId) data.templateId = Number(templateId);
|
|
1880
|
+
if (notes) data.notes = notes;
|
|
1881
|
+
await client2.saveNewsletterToArchive(data);
|
|
1882
|
+
return {
|
|
1883
|
+
content: [
|
|
1884
|
+
{
|
|
1885
|
+
type: "text",
|
|
1886
|
+
text: `Newsletter "${subject}" has been saved to the archive successfully.`
|
|
1887
|
+
}
|
|
1888
|
+
]
|
|
1889
|
+
};
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1892
|
+
throw new Error(`Failed to save newsletter: ${message}`);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// src/tools/calendar.ts
|
|
1899
|
+
import { z as z10 } from "zod";
|
|
1900
|
+
function registerCalendarTools(server2, client2) {
|
|
1901
|
+
server2.tool(
|
|
1902
|
+
"get_calendar",
|
|
1903
|
+
"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.",
|
|
1904
|
+
{
|
|
1905
|
+
from: z10.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
1906
|
+
to: z10.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
1907
|
+
status: z10.string().optional().describe("Filter by status: scheduled, published, draft, failed"),
|
|
1908
|
+
type: z10.string().optional().describe("Filter by type: social_post or newsletter")
|
|
1909
|
+
},
|
|
1910
|
+
{
|
|
1911
|
+
title: "Get Content Calendar",
|
|
1912
|
+
readOnlyHint: true,
|
|
1913
|
+
destructiveHint: false,
|
|
1914
|
+
idempotentHint: true,
|
|
1915
|
+
openWorldHint: false
|
|
1916
|
+
},
|
|
1917
|
+
async (args) => {
|
|
1918
|
+
const params = {};
|
|
1919
|
+
if (args.from) params.from = args.from;
|
|
1920
|
+
if (args.to) params.to = args.to;
|
|
1921
|
+
if (args.status) params.status = args.status;
|
|
1922
|
+
if (args.type) params.type = args.type;
|
|
1923
|
+
const data = await client2.getCalendar(params);
|
|
1924
|
+
const items = data?.items ?? [];
|
|
1925
|
+
const scheduled = items.filter((i) => i.status === "scheduled");
|
|
1926
|
+
const published = items.filter((i) => i.status === "published");
|
|
1927
|
+
let text = "## Content Calendar\n\n";
|
|
1928
|
+
if (scheduled.length > 0) {
|
|
1929
|
+
text += "### Scheduled\n";
|
|
1930
|
+
for (const item of scheduled) {
|
|
1931
|
+
const date = item.scheduled_for ? new Date(item.scheduled_for).toLocaleString() : "TBD";
|
|
1932
|
+
if (item.type === "newsletter") {
|
|
1933
|
+
text += `\u{1F4E7} ${date} -- Newsletter: "${item.content_preview}"
|
|
1934
|
+
`;
|
|
1935
|
+
} else {
|
|
1936
|
+
const platforms = (item.platforms ?? []).join(", ");
|
|
1937
|
+
text += `\u{1F4C5} ${date} -- ${platforms} -- "${item.content_preview}"
|
|
1938
|
+
`;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
text += "\n";
|
|
1942
|
+
} else {
|
|
1943
|
+
text += "### Scheduled\nNo scheduled content.\n\n";
|
|
1944
|
+
}
|
|
1945
|
+
if (published.length > 0) {
|
|
1946
|
+
text += "### Recently Published\n";
|
|
1947
|
+
for (const item of published.slice(0, 10)) {
|
|
1948
|
+
const date = item.published_at ? new Date(item.published_at).toLocaleString() : "";
|
|
1949
|
+
if (item.type === "newsletter") {
|
|
1950
|
+
text += `\u2705 ${date} -- Newsletter: "${item.content_preview}"
|
|
1951
|
+
`;
|
|
1952
|
+
} else {
|
|
1953
|
+
const platforms = (item.platforms ?? []).join(", ");
|
|
1954
|
+
text += `\u2705 ${date} -- ${platforms} -- "${item.content_preview}"
|
|
1955
|
+
`;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
text += "\n";
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
const nextSlot = await client2.getNextSlot();
|
|
1962
|
+
if (nextSlot?.scheduledFor) {
|
|
1963
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
1964
|
+
const dayNames = [
|
|
1965
|
+
"Sunday",
|
|
1966
|
+
"Monday",
|
|
1967
|
+
"Tuesday",
|
|
1968
|
+
"Wednesday",
|
|
1969
|
+
"Thursday",
|
|
1970
|
+
"Friday",
|
|
1971
|
+
"Saturday"
|
|
1972
|
+
];
|
|
1973
|
+
text += `### Queue: Next slot
|
|
1974
|
+
\u23ED\uFE0F ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
1975
|
+
`;
|
|
1976
|
+
}
|
|
1977
|
+
} catch {
|
|
1978
|
+
}
|
|
1979
|
+
return {
|
|
1980
|
+
content: [{ type: "text", text }]
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
);
|
|
1984
|
+
server2.tool(
|
|
1985
|
+
"get_queue",
|
|
1986
|
+
"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.",
|
|
1987
|
+
{},
|
|
1988
|
+
{
|
|
1989
|
+
title: "Get Posting Queue",
|
|
1990
|
+
readOnlyHint: true,
|
|
1991
|
+
destructiveHint: false,
|
|
1992
|
+
idempotentHint: true,
|
|
1993
|
+
openWorldHint: false
|
|
1994
|
+
},
|
|
1995
|
+
async () => {
|
|
1996
|
+
const data = await client2.getQueue();
|
|
1997
|
+
const dayNames = [
|
|
1998
|
+
"Sunday",
|
|
1999
|
+
"Monday",
|
|
2000
|
+
"Tuesday",
|
|
2001
|
+
"Wednesday",
|
|
2002
|
+
"Thursday",
|
|
2003
|
+
"Friday",
|
|
2004
|
+
"Saturday"
|
|
2005
|
+
];
|
|
2006
|
+
let text = "## Posting Queue\n\n";
|
|
2007
|
+
text += `Timezone: ${data?.timezone ?? "UTC"}
|
|
2008
|
+
`;
|
|
2009
|
+
text += `Active: ${data?.active ? "Yes" : "No"}
|
|
2010
|
+
|
|
2011
|
+
`;
|
|
2012
|
+
const slots = data?.slots ?? [];
|
|
2013
|
+
if (slots.length === 0) {
|
|
2014
|
+
text += "No queue slots configured.\n";
|
|
2015
|
+
} else {
|
|
2016
|
+
text += "### Weekly Schedule\n";
|
|
2017
|
+
for (const slot of slots) {
|
|
2018
|
+
const day = dayNames[slot.dayOfWeek] ?? `Day ${slot.dayOfWeek}`;
|
|
2019
|
+
const platforms = (slot.platforms ?? []).map((p) => `[${p}]`).join(" ");
|
|
2020
|
+
text += `${day}: ${slot.time} ${platforms}
|
|
2021
|
+
`;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
try {
|
|
2025
|
+
const nextSlot = await client2.getNextSlot();
|
|
2026
|
+
if (nextSlot?.scheduledFor) {
|
|
2027
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
2028
|
+
text += `
|
|
2029
|
+
Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
2030
|
+
`;
|
|
2031
|
+
}
|
|
2032
|
+
} catch {
|
|
2033
|
+
}
|
|
2034
|
+
return {
|
|
2035
|
+
content: [{ type: "text", text }]
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
server2.tool(
|
|
2040
|
+
"schedule_to_queue",
|
|
2041
|
+
"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.",
|
|
2042
|
+
{
|
|
2043
|
+
content: z10.string().describe("The text content of the post"),
|
|
2044
|
+
platforms: z10.array(z10.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
2045
|
+
media_urls: z10.array(z10.string()).optional().describe("Public URLs of media to attach"),
|
|
2046
|
+
confirmed: z10.boolean().optional().describe(
|
|
2047
|
+
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
2048
|
+
)
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
title: "Schedule Post to Queue",
|
|
2052
|
+
readOnlyHint: false,
|
|
2053
|
+
destructiveHint: false,
|
|
2054
|
+
idempotentHint: false,
|
|
2055
|
+
openWorldHint: true
|
|
2056
|
+
},
|
|
2057
|
+
async (args) => {
|
|
2058
|
+
const nextSlot = await client2.getNextSlot();
|
|
2059
|
+
if (!nextSlot?.scheduledFor) {
|
|
2060
|
+
return {
|
|
2061
|
+
content: [
|
|
2062
|
+
{
|
|
2063
|
+
type: "text",
|
|
2064
|
+
text: "No queue slots configured. Set up your posting queue first using the dashboard or ask to configure queue slots."
|
|
2065
|
+
}
|
|
2066
|
+
]
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
if (!args.confirmed) {
|
|
2070
|
+
const slotDate2 = new Date(nextSlot.scheduledFor);
|
|
2071
|
+
const preview = `## Queue Post Preview
|
|
2072
|
+
|
|
2073
|
+
**Content:** "${args.content}"
|
|
2074
|
+
**Platforms:** ${args.platforms.join(", ")}
|
|
2075
|
+
**Next queue slot:** ${slotDate2.toLocaleString()}
|
|
2076
|
+
` + (args.media_urls?.length ? `**Media:** ${args.media_urls.length} file(s)
|
|
2077
|
+
` : "") + `
|
|
2078
|
+
Call this tool again with confirmed=true to schedule.`;
|
|
2079
|
+
return { content: [{ type: "text", text: preview }] };
|
|
2080
|
+
}
|
|
2081
|
+
const body = {
|
|
2082
|
+
content: args.content,
|
|
2083
|
+
platforms: args.platforms.map((p) => ({ platform: p })),
|
|
2084
|
+
scheduledFor: nextSlot.scheduledFor,
|
|
2085
|
+
timezone: nextSlot.timezone ?? "UTC"
|
|
2086
|
+
};
|
|
2087
|
+
if (args.media_urls?.length) {
|
|
2088
|
+
body.mediaItems = args.media_urls.map((url) => ({
|
|
2089
|
+
type: url.match(/\.(mp4|mov|avi|webm)$/i) ? "video" : "image",
|
|
2090
|
+
url
|
|
2091
|
+
}));
|
|
2092
|
+
}
|
|
2093
|
+
const result = await client2.createPost(body);
|
|
2094
|
+
const slotDate = new Date(nextSlot.scheduledFor);
|
|
2095
|
+
return {
|
|
2096
|
+
content: [
|
|
2097
|
+
{
|
|
2098
|
+
type: "text",
|
|
2099
|
+
text: `Post scheduled to queue slot: ${slotDate.toLocaleString()}
|
|
2100
|
+
|
|
2101
|
+
${JSON.stringify(result, null, 2)}`
|
|
2102
|
+
}
|
|
2103
|
+
]
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/tools/notifications.ts
|
|
2110
|
+
import { z as z11 } from "zod";
|
|
2111
|
+
function timeAgo(dateStr) {
|
|
2112
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2113
|
+
const minutes = Math.floor(diff / 6e4);
|
|
2114
|
+
if (minutes < 1) return "just now";
|
|
2115
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
2116
|
+
const hours = Math.floor(minutes / 60);
|
|
2117
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2118
|
+
const days = Math.floor(hours / 24);
|
|
2119
|
+
return `${days}d ago`;
|
|
2120
|
+
}
|
|
2121
|
+
function registerNotificationTools(server2, client2) {
|
|
2122
|
+
server2.tool(
|
|
2123
|
+
"get_notifications",
|
|
2124
|
+
"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.",
|
|
2125
|
+
{
|
|
2126
|
+
unread_only: z11.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
2127
|
+
limit: z11.number().optional().describe("Max notifications to return. Default 10.")
|
|
2128
|
+
},
|
|
2129
|
+
{
|
|
2130
|
+
title: "Get Notifications",
|
|
2131
|
+
readOnlyHint: true,
|
|
2132
|
+
destructiveHint: false,
|
|
2133
|
+
idempotentHint: true,
|
|
2134
|
+
openWorldHint: false
|
|
2135
|
+
},
|
|
2136
|
+
async (args) => {
|
|
2137
|
+
const params = {};
|
|
2138
|
+
if (args.unread_only) params.unread = "true";
|
|
2139
|
+
if (args.limit) params.limit = String(args.limit);
|
|
2140
|
+
else params.limit = "10";
|
|
2141
|
+
const result = await client2.getNotifications(params);
|
|
2142
|
+
const notifications = result.notifications ?? [];
|
|
2143
|
+
const unreadCount = result.unread_count ?? 0;
|
|
2144
|
+
if (notifications.length === 0) {
|
|
2145
|
+
return {
|
|
2146
|
+
content: [{
|
|
2147
|
+
type: "text",
|
|
2148
|
+
text: `## Notifications${args.unread_only ? " (unread)" : ""}
|
|
2149
|
+
|
|
2150
|
+
No notifications found. All clear!`
|
|
2151
|
+
}]
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
const severityIcon = {
|
|
2155
|
+
success: "\u{1F7E2}",
|
|
2156
|
+
warning: "\u{1F7E1}",
|
|
2157
|
+
error: "\u{1F534}",
|
|
2158
|
+
info: "\u{1F535}"
|
|
2159
|
+
};
|
|
2160
|
+
const lines = notifications.map((n) => {
|
|
2161
|
+
const icon = severityIcon[n.severity] ?? "\u2B1C";
|
|
2162
|
+
const time = n.createdAt ? timeAgo(n.createdAt) : "";
|
|
2163
|
+
let line = `${icon} **${time}** \u2014 ${n.title}
|
|
2164
|
+
"${n.message}"`;
|
|
2165
|
+
if (n.actionUrl || n.actionLabel) {
|
|
2166
|
+
const action = n.actionLabel || "View";
|
|
2167
|
+
if (n.resourceType === "post" && n.resourceId) {
|
|
2168
|
+
line += `
|
|
2169
|
+
Action: ${action} \u2192 retry_post with post_id "${n.resourceId}"`;
|
|
2170
|
+
} else {
|
|
2171
|
+
line += `
|
|
2172
|
+
Action: ${action} \u2192 ${n.actionUrl}`;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return line;
|
|
2176
|
+
});
|
|
2177
|
+
const header = `## Notifications (${unreadCount} unread)
|
|
2178
|
+
|
|
2179
|
+
`;
|
|
2180
|
+
const footer = `
|
|
2181
|
+
|
|
2182
|
+
Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" : ""} notifications.`;
|
|
2183
|
+
return {
|
|
2184
|
+
content: [{
|
|
2185
|
+
type: "text",
|
|
2186
|
+
text: header + lines.join("\n\n") + footer
|
|
2187
|
+
}]
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
779
2193
|
// src/index.ts
|
|
780
2194
|
var apiKey = process.env.BUZZPOSTER_API_KEY;
|
|
781
2195
|
var apiUrl = process.env.BUZZPOSTER_API_URL ?? "https://api.buzzposter.com";
|
|
@@ -801,5 +2215,11 @@ registerMediaTools(server, client);
|
|
|
801
2215
|
registerNewsletterTools(server, client);
|
|
802
2216
|
registerRssTools(server, client);
|
|
803
2217
|
registerAccountInfoTool(server, client);
|
|
2218
|
+
registerBrandVoiceTools(server, client);
|
|
2219
|
+
registerKnowledgeTools(server, client);
|
|
2220
|
+
registerAudienceTools(server, client);
|
|
2221
|
+
registerNewsletterTemplateTools(server, client);
|
|
2222
|
+
registerCalendarTools(server, client);
|
|
2223
|
+
registerNotificationTools(server, client);
|
|
804
2224
|
var transport = new StdioServerTransport();
|
|
805
2225
|
await server.connect(transport);
|