@buzzposter/mcp 0.1.17 → 0.2.0
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 +1416 -921
- package/dist/newsletter-studio.html +620 -0
- package/dist/tools.d.ts +31 -11
- package/dist/tools.js +1383 -898
- package/package.json +15 -6
package/dist/index.js
CHANGED
|
@@ -12,8 +12,8 @@ var BuzzPosterClient = class {
|
|
|
12
12
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
13
13
|
this.apiKey = config.apiKey;
|
|
14
14
|
}
|
|
15
|
-
async request(method,
|
|
16
|
-
const url = new URL(`${this.baseUrl}${
|
|
15
|
+
async request(method, path2, body, query) {
|
|
16
|
+
const url = new URL(`${this.baseUrl}${path2}`);
|
|
17
17
|
if (query) {
|
|
18
18
|
for (const [k, v] of Object.entries(query)) {
|
|
19
19
|
if (v !== void 0 && v !== "") url.searchParams.set(k, v);
|
|
@@ -170,15 +170,8 @@ var BuzzPosterClient = class {
|
|
|
170
170
|
async listPostTemplates() {
|
|
171
171
|
return this.request("GET", "/api/v1/newsletters/post-templates");
|
|
172
172
|
}
|
|
173
|
-
async
|
|
174
|
-
return this.request("GET", "/api/v1/newsletters/
|
|
175
|
-
}
|
|
176
|
-
// Knowledge
|
|
177
|
-
async listKnowledge(params) {
|
|
178
|
-
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
179
|
-
}
|
|
180
|
-
async createKnowledge(data) {
|
|
181
|
-
return this.request("POST", "/api/v1/knowledge", data);
|
|
173
|
+
async listEmailTemplates() {
|
|
174
|
+
return this.request("GET", "/api/v1/newsletters/email-templates");
|
|
182
175
|
}
|
|
183
176
|
// Brand Voice
|
|
184
177
|
async getBrandVoice() {
|
|
@@ -188,9 +181,15 @@ var BuzzPosterClient = class {
|
|
|
188
181
|
return this.request("PUT", "/api/v1/brand-voice", data);
|
|
189
182
|
}
|
|
190
183
|
// Audiences
|
|
184
|
+
async listAudiences() {
|
|
185
|
+
return this.request("GET", "/api/v1/audiences");
|
|
186
|
+
}
|
|
191
187
|
async getAudience(id) {
|
|
192
188
|
return this.request("GET", `/api/v1/audiences/${id}`);
|
|
193
189
|
}
|
|
190
|
+
async getAudienceByName(name) {
|
|
191
|
+
return this.request("GET", `/api/v1/audiences/by-name/${encodeURIComponent(name)}`);
|
|
192
|
+
}
|
|
194
193
|
async getDefaultAudience() {
|
|
195
194
|
return this.request("GET", "/api/v1/audiences/default");
|
|
196
195
|
}
|
|
@@ -200,6 +199,9 @@ var BuzzPosterClient = class {
|
|
|
200
199
|
async updateAudience(id, data) {
|
|
201
200
|
return this.request("PUT", `/api/v1/audiences/${id}`, data);
|
|
202
201
|
}
|
|
202
|
+
async deleteAudience(id) {
|
|
203
|
+
return this.request("DELETE", `/api/v1/audiences/${id}`);
|
|
204
|
+
}
|
|
203
205
|
// Newsletter Templates
|
|
204
206
|
async getTemplate(id) {
|
|
205
207
|
if (id) return this.request("GET", `/api/v1/templates/${id}`);
|
|
@@ -220,16 +222,6 @@ var BuzzPosterClient = class {
|
|
|
220
222
|
async duplicateTemplate(id) {
|
|
221
223
|
return this.request("POST", "/api/v1/templates/" + id + "/duplicate");
|
|
222
224
|
}
|
|
223
|
-
// Newsletter Archive
|
|
224
|
-
async listNewsletterArchive(params) {
|
|
225
|
-
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
226
|
-
}
|
|
227
|
-
async getArchivedNewsletter(id) {
|
|
228
|
-
return this.request("GET", `/api/v1/newsletter-archive/${id}`);
|
|
229
|
-
}
|
|
230
|
-
async saveNewsletterToArchive(data) {
|
|
231
|
-
return this.request("POST", "/api/v1/newsletter-archive", data);
|
|
232
|
-
}
|
|
233
225
|
// Calendar
|
|
234
226
|
async getCalendar(params) {
|
|
235
227
|
return this.request("GET", "/api/v1/calendar", void 0, params);
|
|
@@ -291,6 +283,37 @@ var BuzzPosterClient = class {
|
|
|
291
283
|
async getPublishingRules() {
|
|
292
284
|
return this.request("GET", "/api/v1/publishing-rules");
|
|
293
285
|
}
|
|
286
|
+
// Ghost
|
|
287
|
+
async ghostSetup(data) {
|
|
288
|
+
return this.request("POST", "/api/v1/ghost/setup", data);
|
|
289
|
+
}
|
|
290
|
+
async ghostCreatePost(data) {
|
|
291
|
+
return this.request("POST", "/api/v1/ghost/posts", data);
|
|
292
|
+
}
|
|
293
|
+
async ghostUpdatePost(id, data) {
|
|
294
|
+
return this.request("PUT", `/api/v1/ghost/posts/${id}`, data);
|
|
295
|
+
}
|
|
296
|
+
async ghostGetPost(id) {
|
|
297
|
+
return this.request("GET", `/api/v1/ghost/posts/${id}`);
|
|
298
|
+
}
|
|
299
|
+
async ghostListPosts(params) {
|
|
300
|
+
return this.request("GET", "/api/v1/ghost/posts", void 0, params);
|
|
301
|
+
}
|
|
302
|
+
async ghostDeletePost(id) {
|
|
303
|
+
return this.request("DELETE", `/api/v1/ghost/posts/${id}`);
|
|
304
|
+
}
|
|
305
|
+
async ghostPublishPost(id, data) {
|
|
306
|
+
return this.request("POST", `/api/v1/ghost/posts/${id}/publish`, data);
|
|
307
|
+
}
|
|
308
|
+
async ghostSendNewsletter(id, data) {
|
|
309
|
+
return this.request("POST", `/api/v1/ghost/posts/${id}/newsletter`, data);
|
|
310
|
+
}
|
|
311
|
+
async ghostListMembers(params) {
|
|
312
|
+
return this.request("GET", "/api/v1/ghost/members", void 0, params);
|
|
313
|
+
}
|
|
314
|
+
async ghostListNewsletters() {
|
|
315
|
+
return this.request("GET", "/api/v1/ghost/newsletters");
|
|
316
|
+
}
|
|
294
317
|
// Newsletter Validation
|
|
295
318
|
async validateNewsletter(data) {
|
|
296
319
|
return this.request("POST", "/api/v1/newsletters/validate", data);
|
|
@@ -303,6 +326,34 @@ var BuzzPosterClient = class {
|
|
|
303
326
|
async testBroadcast(id, data) {
|
|
304
327
|
return this.request("POST", `/api/v1/newsletters/broadcasts/${id}/test`, data);
|
|
305
328
|
}
|
|
329
|
+
// WordPress
|
|
330
|
+
async wpCreatePost(data) {
|
|
331
|
+
return this.request("POST", "/api/v1/wordpress/posts", data);
|
|
332
|
+
}
|
|
333
|
+
async wpUpdatePost(id, data) {
|
|
334
|
+
return this.request("PUT", `/api/v1/wordpress/posts/${id}`, data);
|
|
335
|
+
}
|
|
336
|
+
async wpGetPost(id) {
|
|
337
|
+
return this.request("GET", `/api/v1/wordpress/posts/${id}`);
|
|
338
|
+
}
|
|
339
|
+
async wpListPosts(params) {
|
|
340
|
+
return this.request("GET", "/api/v1/wordpress/posts", void 0, params);
|
|
341
|
+
}
|
|
342
|
+
async wpDeletePost(id, params) {
|
|
343
|
+
return this.request("DELETE", `/api/v1/wordpress/posts/${id}`, void 0, params);
|
|
344
|
+
}
|
|
345
|
+
async wpUploadMedia(data) {
|
|
346
|
+
return this.request("POST", "/api/v1/wordpress/media", data);
|
|
347
|
+
}
|
|
348
|
+
async wpListCategories() {
|
|
349
|
+
return this.request("GET", "/api/v1/wordpress/categories");
|
|
350
|
+
}
|
|
351
|
+
async wpListTags() {
|
|
352
|
+
return this.request("GET", "/api/v1/wordpress/tags");
|
|
353
|
+
}
|
|
354
|
+
async wpCreatePage(data) {
|
|
355
|
+
return this.request("POST", "/api/v1/wordpress/pages", data);
|
|
356
|
+
}
|
|
306
357
|
};
|
|
307
358
|
|
|
308
359
|
// src/tools/posts.ts
|
|
@@ -310,7 +361,7 @@ import { z } from "zod";
|
|
|
310
361
|
function registerPostTools(server2, client2) {
|
|
311
362
|
server2.tool(
|
|
312
363
|
"post",
|
|
313
|
-
"Publish a post to one or more social platforms.
|
|
364
|
+
"Publish a post to one or more social platforms. Always call get_brand_voice and get_audience first to match tone. Always set confirmed=false first to generate a preview \u2014 never publish without explicit user approval.",
|
|
314
365
|
{
|
|
315
366
|
content: z.string().optional().describe("The text content of the post"),
|
|
316
367
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -404,7 +455,7 @@ Call this tool again with confirmed=true to proceed.`;
|
|
|
404
455
|
);
|
|
405
456
|
server2.tool(
|
|
406
457
|
"schedule_post",
|
|
407
|
-
"Schedule a post for future publication.
|
|
458
|
+
"Schedule a post for future publication. Always call get_brand_voice and get_audience first to match tone. Always set confirmed=false first to generate a preview \u2014 never schedule without explicit user approval.",
|
|
408
459
|
{
|
|
409
460
|
content: z.string().optional().describe("The text content of the post"),
|
|
410
461
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -497,8 +548,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
497
548
|
const body = {
|
|
498
549
|
content: args.content,
|
|
499
550
|
platforms: args.platforms.map((p) => ({ platform: p })),
|
|
500
|
-
|
|
501
|
-
status: "draft"
|
|
551
|
+
isDraft: true
|
|
502
552
|
};
|
|
503
553
|
if (args.media_urls?.length) {
|
|
504
554
|
body.mediaItems = args.media_urls.map((url) => ({
|
|
@@ -511,7 +561,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
511
561
|
return {
|
|
512
562
|
content: [{
|
|
513
563
|
type: "text",
|
|
514
|
-
text: `ERROR: Post was saved with status "${result.status}" instead of "draft". Post ID: ${result.id ?? "unknown"}. Check
|
|
564
|
+
text: `ERROR: Post was saved with status "${result.status}" instead of "draft". Post ID: ${result.id ?? "unknown"}. Check your BuzzPoster dashboard immediately.`
|
|
515
565
|
}],
|
|
516
566
|
isError: true
|
|
517
567
|
};
|
|
@@ -841,7 +891,89 @@ This will post a public reply to the review. Call this tool again with confirmed
|
|
|
841
891
|
|
|
842
892
|
// src/tools/media.ts
|
|
843
893
|
import { z as z4 } from "zod";
|
|
894
|
+
var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
895
|
+
"image/png",
|
|
896
|
+
"image/jpeg",
|
|
897
|
+
"image/gif",
|
|
898
|
+
"image/webp",
|
|
899
|
+
"image/svg+xml"
|
|
900
|
+
]);
|
|
901
|
+
var MAX_THUMBNAIL_BYTES = 15e4;
|
|
902
|
+
async function fetchImageAsBase64(url) {
|
|
903
|
+
try {
|
|
904
|
+
const res = await fetch(url);
|
|
905
|
+
if (!res.ok) return null;
|
|
906
|
+
const contentType = res.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
907
|
+
if (!IMAGE_MIME_TYPES.has(contentType)) return null;
|
|
908
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
909
|
+
if (buffer.byteLength > MAX_THUMBNAIL_BYTES) return null;
|
|
910
|
+
return {
|
|
911
|
+
data: buffer.toString("base64"),
|
|
912
|
+
mimeType: contentType
|
|
913
|
+
};
|
|
914
|
+
} catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
844
918
|
function registerMediaTools(server2, client2) {
|
|
919
|
+
server2.tool(
|
|
920
|
+
"get_assets",
|
|
921
|
+
"Get brand assets from the media library with visual thumbnails. Returns inline image previews plus full-resolution URLs for use in posts.",
|
|
922
|
+
{
|
|
923
|
+
limit: z4.number().optional().describe("Max number of assets to return with image previews. Default 10, max 20.")
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
title: "Get Brand Assets",
|
|
927
|
+
readOnlyHint: true,
|
|
928
|
+
destructiveHint: false,
|
|
929
|
+
idempotentHint: true,
|
|
930
|
+
openWorldHint: false
|
|
931
|
+
},
|
|
932
|
+
async ({ limit }) => {
|
|
933
|
+
const maxItems = Math.min(limit ?? 10, 20);
|
|
934
|
+
const result = await client2.listMedia();
|
|
935
|
+
const items = result?.media ?? [];
|
|
936
|
+
const images = items.filter((m) => IMAGE_MIME_TYPES.has(m.mimeType)).slice(0, maxItems);
|
|
937
|
+
if (images.length === 0) {
|
|
938
|
+
return {
|
|
939
|
+
content: [
|
|
940
|
+
{
|
|
941
|
+
type: "text",
|
|
942
|
+
text: "No image assets found in the media library. Upload images with upload_from_url first."
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
const thumbnails = await Promise.all(
|
|
948
|
+
images.map((img) => fetchImageAsBase64(img.url))
|
|
949
|
+
);
|
|
950
|
+
const content = [];
|
|
951
|
+
content.push({
|
|
952
|
+
type: "text",
|
|
953
|
+
text: `## Brand Assets (${images.length} images)
|
|
954
|
+
|
|
955
|
+
Use the URLs below when attaching images to posts or newsletters.`
|
|
956
|
+
});
|
|
957
|
+
for (let i = 0; i < images.length; i++) {
|
|
958
|
+
const img = images[i];
|
|
959
|
+
const thumb = thumbnails[i];
|
|
960
|
+
content.push({
|
|
961
|
+
type: "text",
|
|
962
|
+
text: `### ${img.filename}
|
|
963
|
+
- **URL:** ${img.url}
|
|
964
|
+
- **Type:** ${img.mimeType} | **Size:** ${(img.size / 1024).toFixed(1)}KB`
|
|
965
|
+
});
|
|
966
|
+
if (thumb) {
|
|
967
|
+
content.push({
|
|
968
|
+
type: "image",
|
|
969
|
+
data: thumb.data,
|
|
970
|
+
mimeType: thumb.mimeType
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return { content };
|
|
975
|
+
}
|
|
976
|
+
);
|
|
845
977
|
server2.tool(
|
|
846
978
|
"upload_from_url",
|
|
847
979
|
"Upload media from a public URL to storage. Returns a CDN URL for use in posts.",
|
|
@@ -863,11 +995,9 @@ function registerMediaTools(server2, client2) {
|
|
|
863
995
|
filename: args.filename,
|
|
864
996
|
folder: args.folder
|
|
865
997
|
});
|
|
866
|
-
const item = result;
|
|
867
998
|
return {
|
|
868
999
|
content: [
|
|
869
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
870
|
-
...item.type === "image" && item.url && item.mimeType && !item.mimeType.startsWith("video/") ? [{ type: "image", url: item.url, mimeType: item.mimeType }] : []
|
|
1000
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
871
1001
|
]
|
|
872
1002
|
};
|
|
873
1003
|
}
|
|
@@ -885,12 +1015,9 @@ function registerMediaTools(server2, client2) {
|
|
|
885
1015
|
},
|
|
886
1016
|
async () => {
|
|
887
1017
|
const result = await client2.listMedia();
|
|
888
|
-
const media = Array.isArray(result) ? result : result?.media ?? [];
|
|
889
|
-
const imageBlocks = media.filter((m) => m.type === "image" && m.url && m.mimeType && !m.mimeType.startsWith("video/")).map((m) => ({ type: "image", url: m.url, mimeType: m.mimeType }));
|
|
890
1018
|
return {
|
|
891
1019
|
content: [
|
|
892
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
893
|
-
...imageBlocks
|
|
1020
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
894
1021
|
]
|
|
895
1022
|
};
|
|
896
1023
|
}
|
|
@@ -928,7 +1055,37 @@ This will permanently delete this media file. Call this tool again with confirme
|
|
|
928
1055
|
|
|
929
1056
|
// src/tools/newsletter.ts
|
|
930
1057
|
import { z as z5 } from "zod";
|
|
1058
|
+
import {
|
|
1059
|
+
registerAppResource,
|
|
1060
|
+
registerAppTool,
|
|
1061
|
+
RESOURCE_MIME_TYPE
|
|
1062
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
1063
|
+
import * as fs from "fs";
|
|
1064
|
+
import * as path from "path";
|
|
1065
|
+
import { fileURLToPath } from "url";
|
|
1066
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1067
|
+
var NEWSLETTER_STUDIO_RESOURCE = "ui://newsletter-studio/app.html";
|
|
931
1068
|
function registerNewsletterTools(server2, client2, options = {}) {
|
|
1069
|
+
registerAppResource(
|
|
1070
|
+
server2,
|
|
1071
|
+
NEWSLETTER_STUDIO_RESOURCE,
|
|
1072
|
+
NEWSLETTER_STUDIO_RESOURCE,
|
|
1073
|
+
{ mimeType: RESOURCE_MIME_TYPE },
|
|
1074
|
+
async () => {
|
|
1075
|
+
const distDir = __dirname.endsWith("dist") ? __dirname : path.resolve(__dirname, "..", "dist");
|
|
1076
|
+
const htmlPath = path.join(distDir, "newsletter-studio.html");
|
|
1077
|
+
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
1078
|
+
return {
|
|
1079
|
+
contents: [
|
|
1080
|
+
{
|
|
1081
|
+
uri: NEWSLETTER_STUDIO_RESOURCE,
|
|
1082
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
1083
|
+
text: html
|
|
1084
|
+
}
|
|
1085
|
+
]
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
);
|
|
932
1089
|
server2.tool(
|
|
933
1090
|
"list_subscribers",
|
|
934
1091
|
"List email subscribers from the connected ESP.",
|
|
@@ -979,32 +1136,33 @@ function registerNewsletterTools(server2, client2, options = {}) {
|
|
|
979
1136
|
};
|
|
980
1137
|
}
|
|
981
1138
|
);
|
|
982
|
-
|
|
1139
|
+
registerAppTool(
|
|
1140
|
+
server2,
|
|
983
1141
|
"create_newsletter",
|
|
984
|
-
"Push a newsletter to the user's ESP as a draft. Show an HTML preview to the user before calling this.",
|
|
985
|
-
{
|
|
986
|
-
subject: z5.string().describe("Email subject line"),
|
|
987
|
-
content: z5.string().describe("HTML content of the newsletter"),
|
|
988
|
-
preview_text: z5.string().optional().describe("Preview text shown in email clients")
|
|
989
|
-
},
|
|
990
1142
|
{
|
|
991
1143
|
title: "Create Newsletter Draft",
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1144
|
+
description: "Push a newsletter to the user's ESP as a draft. Always call get_brand_voice first. Show an HTML preview to the user before calling this. Use table-based layouts, inline CSS only, 600px max width, email-safe fonts only (Arial, Helvetica, Georgia, Verdana), all images must use absolute URLs, keep under 102KB to avoid Gmail clipping. Optionally pass template_id to use an ESP-native template. Use list_esp_templates to discover available templates.",
|
|
1145
|
+
inputSchema: {
|
|
1146
|
+
subject: z5.string().describe("Email subject line"),
|
|
1147
|
+
content: z5.string().describe("HTML content of the newsletter"),
|
|
1148
|
+
preview_text: z5.string().optional().describe("Preview text shown in email clients"),
|
|
1149
|
+
template_id: z5.string().optional().describe(
|
|
1150
|
+
"ESP-native template ID. For Kit: email_template_id (integer as string). For Beehiiv: post_template_id. Use list_esp_templates to find available templates."
|
|
1151
|
+
)
|
|
1152
|
+
},
|
|
1153
|
+
_meta: { ui: { resourceUri: NEWSLETTER_STUDIO_RESOURCE } }
|
|
996
1154
|
},
|
|
997
1155
|
async (args) => {
|
|
998
|
-
const
|
|
1156
|
+
const payload = {
|
|
999
1157
|
subject: args.subject,
|
|
1000
1158
|
content: args.content,
|
|
1001
1159
|
previewText: args.preview_text
|
|
1002
|
-
}
|
|
1160
|
+
};
|
|
1161
|
+
if (args.template_id) payload.templateId = args.template_id;
|
|
1162
|
+
const result = await client2.createBroadcast(payload);
|
|
1003
1163
|
return {
|
|
1004
1164
|
content: [
|
|
1005
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1006
|
-
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1007
|
-
${args.content}` }] : []
|
|
1165
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1008
1166
|
]
|
|
1009
1167
|
};
|
|
1010
1168
|
}
|
|
@@ -1033,9 +1191,7 @@ ${args.content}` }] : []
|
|
|
1033
1191
|
const result = await client2.updateBroadcast(args.broadcast_id, data);
|
|
1034
1192
|
return {
|
|
1035
1193
|
content: [
|
|
1036
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1037
|
-
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1038
|
-
${args.content}` }] : []
|
|
1194
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1039
1195
|
]
|
|
1040
1196
|
};
|
|
1041
1197
|
}
|
|
@@ -1043,7 +1199,7 @@ ${args.content}` }] : []
|
|
|
1043
1199
|
if (options.allowDirectSend) {
|
|
1044
1200
|
server2.tool(
|
|
1045
1201
|
"send_newsletter",
|
|
1046
|
-
"Send a newsletter to subscribers. This action cannot be undone.
|
|
1202
|
+
"Send a newsletter to subscribers. This action cannot be undone. Always set confirmed=false first \u2014 never send without explicit user approval.",
|
|
1047
1203
|
{
|
|
1048
1204
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1049
1205
|
confirmed: z5.boolean().default(false).describe(
|
|
@@ -1107,7 +1263,7 @@ Call this tool again with confirmed=true to send.`;
|
|
|
1107
1263
|
}
|
|
1108
1264
|
server2.tool(
|
|
1109
1265
|
"schedule_newsletter",
|
|
1110
|
-
"Schedule a newsletter for future send.
|
|
1266
|
+
"Schedule a newsletter for future send. Always set confirmed=false first \u2014 never schedule without explicit user approval.",
|
|
1111
1267
|
{
|
|
1112
1268
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to schedule"),
|
|
1113
1269
|
scheduled_for: z5.string().describe(
|
|
@@ -1272,10 +1428,10 @@ import { z as z6 } from "zod";
|
|
|
1272
1428
|
function registerRssTools(server2, client2) {
|
|
1273
1429
|
server2.tool(
|
|
1274
1430
|
"fetch_feed",
|
|
1275
|
-
"Fetch and parse entries from an RSS or Atom feed URL.",
|
|
1431
|
+
"Fetch and parse all available entries from an RSS or Atom feed URL. Returns up to 100 entries by default.",
|
|
1276
1432
|
{
|
|
1277
1433
|
url: z6.string().describe("The RSS/Atom feed URL to fetch"),
|
|
1278
|
-
limit: z6.number().optional().describe("Maximum number of entries to return (default
|
|
1434
|
+
limit: z6.number().optional().describe("Maximum number of entries to return (default 100)")
|
|
1279
1435
|
},
|
|
1280
1436
|
{
|
|
1281
1437
|
title: "Fetch RSS Feed",
|
|
@@ -1346,7 +1502,7 @@ import { z as z7 } from "zod";
|
|
|
1346
1502
|
function registerBrandVoiceTools(server2, client2) {
|
|
1347
1503
|
server2.tool(
|
|
1348
1504
|
"get_brand_voice",
|
|
1349
|
-
"Get the brand voice profile and
|
|
1505
|
+
"Get the brand voice profile, writing rules, and platform-specific examples. Call this before creating any content to match the user's tone and style.",
|
|
1350
1506
|
{},
|
|
1351
1507
|
{
|
|
1352
1508
|
title: "Get Brand Voice",
|
|
@@ -1391,6 +1547,18 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1391
1547
|
lines.push("(none)");
|
|
1392
1548
|
}
|
|
1393
1549
|
lines.push("");
|
|
1550
|
+
lines.push("### Platform-Specific Examples");
|
|
1551
|
+
if (voice.platformExamples && Object.keys(voice.platformExamples).length > 0) {
|
|
1552
|
+
for (const [platform, examples] of Object.entries(voice.platformExamples)) {
|
|
1553
|
+
lines.push(`**${platform}:**`);
|
|
1554
|
+
examples.forEach((ex, i) => {
|
|
1555
|
+
lines.push(` ${i + 1}. ${ex}`);
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
} else {
|
|
1559
|
+
lines.push("(none)");
|
|
1560
|
+
}
|
|
1561
|
+
lines.push("");
|
|
1394
1562
|
lines.push("### Example Posts");
|
|
1395
1563
|
if (voice.examplePosts && voice.examplePosts.length > 0) {
|
|
1396
1564
|
voice.examplePosts.forEach((post, i) => {
|
|
@@ -1400,6 +1568,9 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1400
1568
|
lines.push("(none)");
|
|
1401
1569
|
}
|
|
1402
1570
|
lines.push("");
|
|
1571
|
+
if (voice.updatedAt) {
|
|
1572
|
+
lines.push(`_Last updated: ${voice.updatedAt}_`);
|
|
1573
|
+
}
|
|
1403
1574
|
return {
|
|
1404
1575
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
1405
1576
|
};
|
|
@@ -1428,7 +1599,8 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1428
1599
|
dos: z7.array(z7.string().max(200)).max(15).optional().describe("Writing rules to follow. Send the FULL list, not just additions."),
|
|
1429
1600
|
donts: z7.array(z7.string().max(200)).max(15).optional().describe("Things to avoid. Send the FULL list, not just additions."),
|
|
1430
1601
|
platform_rules: z7.record(z7.string().max(300)).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars" }'),
|
|
1431
|
-
|
|
1602
|
+
platform_examples: z7.record(z7.array(z7.string().max(500)).max(3)).optional().describe('Per-platform example posts, e.g. { "twitter": ["Example tweet 1", "Example tweet 2"], "linkedin": ["Example post"] }'),
|
|
1603
|
+
example_posts: z7.array(z7.string().max(500)).max(5).optional().describe("General example posts that demonstrate the desired voice"),
|
|
1432
1604
|
confirmed: z7.boolean().default(false).describe(
|
|
1433
1605
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
1434
1606
|
)
|
|
@@ -1459,13 +1631,26 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1459
1631
|
}
|
|
1460
1632
|
payload.platformRules = args.platform_rules;
|
|
1461
1633
|
}
|
|
1634
|
+
if (args.platform_examples !== void 0) {
|
|
1635
|
+
const entries = Object.entries(args.platform_examples);
|
|
1636
|
+
if (entries.length > 6) {
|
|
1637
|
+
return {
|
|
1638
|
+
content: [{
|
|
1639
|
+
type: "text",
|
|
1640
|
+
text: "Maximum 6 platforms for examples allowed."
|
|
1641
|
+
}],
|
|
1642
|
+
isError: true
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
payload.platformExamples = args.platform_examples;
|
|
1646
|
+
}
|
|
1462
1647
|
if (args.example_posts !== void 0) payload.examplePosts = args.example_posts;
|
|
1463
1648
|
if (Object.keys(payload).length === 0) {
|
|
1464
1649
|
return {
|
|
1465
1650
|
content: [
|
|
1466
1651
|
{
|
|
1467
1652
|
type: "text",
|
|
1468
|
-
text: "No fields provided. Specify at least one field to update (name, description, dos, donts, platform_rules, example_posts)."
|
|
1653
|
+
text: "No fields provided. Specify at least one field to update (name, description, dos, donts, platform_rules, platform_examples, example_posts)."
|
|
1469
1654
|
}
|
|
1470
1655
|
],
|
|
1471
1656
|
isError: true
|
|
@@ -1508,6 +1693,17 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1508
1693
|
}
|
|
1509
1694
|
lines2.push("");
|
|
1510
1695
|
}
|
|
1696
|
+
if (payload.platformExamples) {
|
|
1697
|
+
const examples = payload.platformExamples;
|
|
1698
|
+
lines2.push(`### Platform-Specific Examples (${Object.keys(examples).length} platforms)`);
|
|
1699
|
+
for (const [platform, exList] of Object.entries(examples)) {
|
|
1700
|
+
lines2.push(`**${platform}:**`);
|
|
1701
|
+
exList.forEach((ex, i) => {
|
|
1702
|
+
lines2.push(` ${i + 1}. ${ex}`);
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
lines2.push("");
|
|
1706
|
+
}
|
|
1511
1707
|
if (payload.examplePosts) {
|
|
1512
1708
|
const posts = payload.examplePosts;
|
|
1513
1709
|
lines2.push(`### Example Posts (${posts.length})`);
|
|
@@ -1532,6 +1728,7 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1532
1728
|
if (payload.dos) summary.push(`${payload.dos.length} do's`);
|
|
1533
1729
|
if (payload.donts) summary.push(`${payload.donts.length} don'ts`);
|
|
1534
1730
|
if (payload.platformRules) summary.push(`${Object.keys(payload.platformRules).length} platform rules`);
|
|
1731
|
+
if (payload.platformExamples) summary.push(`${Object.keys(payload.platformExamples).length} platform example sets`);
|
|
1535
1732
|
if (payload.examplePosts) summary.push(`${payload.examplePosts.length} example posts`);
|
|
1536
1733
|
lines.push(`**Updated:** ${summary.join(", ")}`);
|
|
1537
1734
|
return {
|
|
@@ -1541,15 +1738,70 @@ function registerBrandVoiceTools(server2, client2) {
|
|
|
1541
1738
|
);
|
|
1542
1739
|
}
|
|
1543
1740
|
|
|
1544
|
-
// src/tools/
|
|
1741
|
+
// src/tools/audience.ts
|
|
1545
1742
|
import { z as z8 } from "zod";
|
|
1546
|
-
function
|
|
1743
|
+
function formatAudience(audience) {
|
|
1744
|
+
const lines = [];
|
|
1745
|
+
lines.push(`## ${audience.name}${audience.isDefault ? " (default)" : ""}`);
|
|
1746
|
+
lines.push("");
|
|
1747
|
+
if (audience.description) {
|
|
1748
|
+
lines.push("### Description");
|
|
1749
|
+
lines.push(audience.description);
|
|
1750
|
+
lines.push("");
|
|
1751
|
+
}
|
|
1752
|
+
if (audience.demographics) {
|
|
1753
|
+
lines.push("### Demographics");
|
|
1754
|
+
lines.push(audience.demographics);
|
|
1755
|
+
lines.push("");
|
|
1756
|
+
}
|
|
1757
|
+
if (audience.painPoints && audience.painPoints.length > 0) {
|
|
1758
|
+
lines.push("### Pain Points");
|
|
1759
|
+
for (const point of audience.painPoints) {
|
|
1760
|
+
lines.push(`- ${point}`);
|
|
1761
|
+
}
|
|
1762
|
+
lines.push("");
|
|
1763
|
+
}
|
|
1764
|
+
if (audience.motivations && audience.motivations.length > 0) {
|
|
1765
|
+
lines.push("### Motivations");
|
|
1766
|
+
for (const motivation of audience.motivations) {
|
|
1767
|
+
lines.push(`- ${motivation}`);
|
|
1768
|
+
}
|
|
1769
|
+
lines.push("");
|
|
1770
|
+
}
|
|
1771
|
+
if (audience.platforms && audience.platforms.length > 0) {
|
|
1772
|
+
lines.push("### Active Platforms");
|
|
1773
|
+
lines.push(audience.platforms.join(", "));
|
|
1774
|
+
lines.push("");
|
|
1775
|
+
}
|
|
1776
|
+
if (audience.toneNotes) {
|
|
1777
|
+
lines.push("### Tone Notes");
|
|
1778
|
+
lines.push(audience.toneNotes);
|
|
1779
|
+
lines.push("");
|
|
1780
|
+
}
|
|
1781
|
+
if (audience.contentPreferences) {
|
|
1782
|
+
lines.push("### Content Preferences");
|
|
1783
|
+
lines.push(audience.contentPreferences);
|
|
1784
|
+
lines.push("");
|
|
1785
|
+
}
|
|
1786
|
+
return lines.join("\n");
|
|
1787
|
+
}
|
|
1788
|
+
var audienceFields = {
|
|
1789
|
+
name: z8.string().max(100).describe("Audience name (must be unique per account)"),
|
|
1790
|
+
description: z8.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
1791
|
+
demographics: z8.string().max(500).optional().describe("Demographic details"),
|
|
1792
|
+
pain_points: z8.array(z8.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
1793
|
+
motivations: z8.array(z8.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
1794
|
+
preferred_platforms: z8.array(z8.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
1795
|
+
tone_notes: z8.string().max(500).optional().describe("How to speak to this audience"),
|
|
1796
|
+
content_preferences: z8.string().max(500).optional().describe("What content formats and topics resonate")
|
|
1797
|
+
};
|
|
1798
|
+
function registerAudienceTools(server2, client2) {
|
|
1547
1799
|
server2.tool(
|
|
1548
|
-
"
|
|
1549
|
-
"
|
|
1800
|
+
"list_audiences",
|
|
1801
|
+
"List all audience profiles. Call this to see which audiences are available before creating content.",
|
|
1550
1802
|
{},
|
|
1551
1803
|
{
|
|
1552
|
-
title: "
|
|
1804
|
+
title: "List Audiences",
|
|
1553
1805
|
readOnlyHint: true,
|
|
1554
1806
|
destructiveHint: false,
|
|
1555
1807
|
idempotentHint: true,
|
|
@@ -1557,230 +1809,171 @@ function registerKnowledgeTools(server2, client2) {
|
|
|
1557
1809
|
},
|
|
1558
1810
|
async () => {
|
|
1559
1811
|
try {
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
if (items.length === 0) {
|
|
1812
|
+
const audiences = await client2.listAudiences();
|
|
1813
|
+
if (!audiences || audiences.length === 0) {
|
|
1563
1814
|
return {
|
|
1564
1815
|
content: [
|
|
1565
1816
|
{
|
|
1566
1817
|
type: "text",
|
|
1567
|
-
text: "
|
|
1818
|
+
text: "No audience profiles configured. Use create_audience to add one."
|
|
1568
1819
|
}
|
|
1569
1820
|
]
|
|
1570
1821
|
};
|
|
1571
1822
|
}
|
|
1572
|
-
const totalChars = items.reduce(
|
|
1573
|
-
(sum, item) => sum + item.content.length,
|
|
1574
|
-
0
|
|
1575
|
-
);
|
|
1576
|
-
const shouldTruncate = totalChars > 1e4;
|
|
1577
1823
|
const lines = [];
|
|
1578
|
-
lines.push(`##
|
|
1824
|
+
lines.push(`## Audience Profiles (${audiences.length})`);
|
|
1579
1825
|
lines.push("");
|
|
1580
|
-
for (const
|
|
1581
|
-
lines.push(
|
|
1582
|
-
if (item.tags && item.tags.length > 0) {
|
|
1583
|
-
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1584
|
-
}
|
|
1585
|
-
if (shouldTruncate) {
|
|
1586
|
-
lines.push(item.content.slice(0, 500) + " [truncated]");
|
|
1587
|
-
} else {
|
|
1588
|
-
lines.push(item.content);
|
|
1589
|
-
}
|
|
1590
|
-
lines.push("");
|
|
1826
|
+
for (const a of audiences) {
|
|
1827
|
+
lines.push(`- **${a.name}**${a.isDefault ? " (default)" : ""}: ${a.description || "(no description)"}`);
|
|
1591
1828
|
}
|
|
1829
|
+
lines.push("");
|
|
1830
|
+
lines.push("Use get_audience with a name to see the full profile.");
|
|
1592
1831
|
return {
|
|
1593
1832
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
1594
1833
|
};
|
|
1595
1834
|
} catch (error) {
|
|
1596
1835
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1597
|
-
throw new Error(`Failed to
|
|
1836
|
+
throw new Error(`Failed to list audiences: ${message}`);
|
|
1598
1837
|
}
|
|
1599
1838
|
}
|
|
1600
1839
|
);
|
|
1601
1840
|
server2.tool(
|
|
1602
|
-
"
|
|
1603
|
-
"
|
|
1841
|
+
"get_audience",
|
|
1842
|
+
"Get a specific audience profile by name. If no name is given, returns the default audience. Call before creating content to understand who you're writing for.",
|
|
1604
1843
|
{
|
|
1605
|
-
|
|
1606
|
-
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
1607
|
-
)
|
|
1844
|
+
name: z8.string().optional().describe("Audience name to look up. If omitted, returns the default audience.")
|
|
1608
1845
|
},
|
|
1609
1846
|
{
|
|
1610
|
-
title: "
|
|
1847
|
+
title: "Get Audience Profile",
|
|
1611
1848
|
readOnlyHint: true,
|
|
1612
1849
|
destructiveHint: false,
|
|
1613
1850
|
idempotentHint: true,
|
|
1614
1851
|
openWorldHint: false
|
|
1615
1852
|
},
|
|
1616
|
-
async ({
|
|
1853
|
+
async ({ name }) => {
|
|
1617
1854
|
try {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1855
|
+
let audience;
|
|
1856
|
+
if (name) {
|
|
1857
|
+
audience = await client2.getAudienceByName(name);
|
|
1858
|
+
} else {
|
|
1859
|
+
audience = await client2.getDefaultAudience();
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
content: [{ type: "text", text: formatAudience(audience) }]
|
|
1863
|
+
};
|
|
1864
|
+
} catch (error) {
|
|
1865
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1866
|
+
if (message.includes("404") || message.includes("No audience") || message.includes("not found")) {
|
|
1621
1867
|
return {
|
|
1622
1868
|
content: [
|
|
1623
1869
|
{
|
|
1624
1870
|
type: "text",
|
|
1625
|
-
text: `No
|
|
1871
|
+
text: name ? `No audience named "${name}" found. Use list_audiences to see available profiles.` : "No audience profiles configured. Use create_audience to add one."
|
|
1626
1872
|
}
|
|
1627
1873
|
]
|
|
1628
1874
|
};
|
|
1629
1875
|
}
|
|
1630
|
-
|
|
1631
|
-
lines.push(
|
|
1632
|
-
`## Knowledge Base Results for "${query}" (${items.length} items)`
|
|
1633
|
-
);
|
|
1634
|
-
lines.push("");
|
|
1635
|
-
for (const item of items) {
|
|
1636
|
-
lines.push(`### ${item.title}`);
|
|
1637
|
-
if (item.tags && item.tags.length > 0) {
|
|
1638
|
-
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1639
|
-
}
|
|
1640
|
-
lines.push(item.content);
|
|
1641
|
-
lines.push("");
|
|
1642
|
-
}
|
|
1643
|
-
return {
|
|
1644
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
1645
|
-
};
|
|
1646
|
-
} catch (error) {
|
|
1647
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1648
|
-
throw new Error(`Failed to search knowledge base: ${message}`);
|
|
1876
|
+
throw error;
|
|
1649
1877
|
}
|
|
1650
1878
|
}
|
|
1651
1879
|
);
|
|
1652
1880
|
server2.tool(
|
|
1653
|
-
"
|
|
1654
|
-
"
|
|
1881
|
+
"create_audience",
|
|
1882
|
+
"Create a new audience profile. Set confirmed=false to preview first.",
|
|
1655
1883
|
{
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1884
|
+
...audienceFields,
|
|
1885
|
+
is_default: z8.boolean().optional().describe("Set as the default audience profile"),
|
|
1886
|
+
confirmed: z8.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
1659
1887
|
},
|
|
1660
1888
|
{
|
|
1661
|
-
title: "
|
|
1889
|
+
title: "Create Audience",
|
|
1662
1890
|
readOnlyHint: false,
|
|
1663
1891
|
destructiveHint: false,
|
|
1664
1892
|
idempotentHint: false,
|
|
1665
1893
|
openWorldHint: false
|
|
1666
1894
|
},
|
|
1667
|
-
async (
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
type: "text",
|
|
1679
|
-
text: `Added "${title}" to the knowledge base.`
|
|
1680
|
-
}
|
|
1681
|
-
]
|
|
1682
|
-
};
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1685
|
-
throw new Error(`Failed to add knowledge item: ${message}`);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
// src/tools/audience.ts
|
|
1692
|
-
import { z as z9 } from "zod";
|
|
1693
|
-
function registerAudienceTools(server2, client2) {
|
|
1694
|
-
server2.tool(
|
|
1695
|
-
"get_audience",
|
|
1696
|
-
"Get the target audience profile. Call before creating content.",
|
|
1697
|
-
{},
|
|
1698
|
-
{
|
|
1699
|
-
title: "Get Audience Profile",
|
|
1700
|
-
readOnlyHint: true,
|
|
1701
|
-
destructiveHint: false,
|
|
1702
|
-
idempotentHint: true,
|
|
1703
|
-
openWorldHint: false
|
|
1704
|
-
},
|
|
1705
|
-
async () => {
|
|
1706
|
-
try {
|
|
1707
|
-
const audience = await client2.getDefaultAudience();
|
|
1895
|
+
async (args) => {
|
|
1896
|
+
const payload = { name: args.name };
|
|
1897
|
+
if (args.description !== void 0) payload.description = args.description;
|
|
1898
|
+
if (args.demographics !== void 0) payload.demographics = args.demographics;
|
|
1899
|
+
if (args.pain_points !== void 0) payload.painPoints = args.pain_points;
|
|
1900
|
+
if (args.motivations !== void 0) payload.motivations = args.motivations;
|
|
1901
|
+
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
1902
|
+
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
1903
|
+
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
1904
|
+
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
1905
|
+
if (args.confirmed !== true) {
|
|
1708
1906
|
const lines = [];
|
|
1709
|
-
lines.push(
|
|
1907
|
+
lines.push("## New Audience Preview");
|
|
1710
1908
|
lines.push("");
|
|
1711
|
-
|
|
1909
|
+
lines.push(`**Name:** ${args.name}`);
|
|
1910
|
+
if (args.is_default) lines.push("**Default:** yes");
|
|
1911
|
+
lines.push("");
|
|
1912
|
+
if (payload.description) {
|
|
1712
1913
|
lines.push("### Description");
|
|
1713
|
-
lines.push(
|
|
1914
|
+
lines.push(String(payload.description));
|
|
1714
1915
|
lines.push("");
|
|
1715
1916
|
}
|
|
1716
|
-
if (
|
|
1917
|
+
if (payload.demographics) {
|
|
1717
1918
|
lines.push("### Demographics");
|
|
1718
|
-
lines.push(
|
|
1919
|
+
lines.push(String(payload.demographics));
|
|
1719
1920
|
lines.push("");
|
|
1720
1921
|
}
|
|
1721
|
-
if (
|
|
1722
|
-
lines.push(
|
|
1723
|
-
for (const
|
|
1724
|
-
lines.push(`- ${point}`);
|
|
1725
|
-
}
|
|
1922
|
+
if (payload.painPoints) {
|
|
1923
|
+
lines.push(`### Pain Points (${payload.painPoints.length})`);
|
|
1924
|
+
for (const p of payload.painPoints) lines.push(`- ${p}`);
|
|
1726
1925
|
lines.push("");
|
|
1727
1926
|
}
|
|
1728
|
-
if (
|
|
1729
|
-
lines.push(
|
|
1730
|
-
for (const
|
|
1731
|
-
lines.push(`- ${motivation}`);
|
|
1732
|
-
}
|
|
1927
|
+
if (payload.motivations) {
|
|
1928
|
+
lines.push(`### Motivations (${payload.motivations.length})`);
|
|
1929
|
+
for (const m of payload.motivations) lines.push(`- ${m}`);
|
|
1733
1930
|
lines.push("");
|
|
1734
1931
|
}
|
|
1735
|
-
if (
|
|
1736
|
-
lines.push(
|
|
1737
|
-
lines.push(
|
|
1932
|
+
if (payload.platforms) {
|
|
1933
|
+
lines.push(`### Platforms`);
|
|
1934
|
+
lines.push(payload.platforms.join(", "));
|
|
1738
1935
|
lines.push("");
|
|
1739
1936
|
}
|
|
1740
|
-
if (
|
|
1937
|
+
if (payload.toneNotes) {
|
|
1741
1938
|
lines.push("### Tone Notes");
|
|
1742
|
-
lines.push(
|
|
1939
|
+
lines.push(String(payload.toneNotes));
|
|
1743
1940
|
lines.push("");
|
|
1744
1941
|
}
|
|
1745
|
-
if (
|
|
1942
|
+
if (payload.contentPreferences) {
|
|
1746
1943
|
lines.push("### Content Preferences");
|
|
1747
|
-
lines.push(
|
|
1944
|
+
lines.push(String(payload.contentPreferences));
|
|
1748
1945
|
lines.push("");
|
|
1749
1946
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
};
|
|
1753
|
-
} catch (error) {
|
|
1754
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1755
|
-
if (message.includes("404") || message.includes("No audience")) {
|
|
1756
|
-
return {
|
|
1757
|
-
content: [
|
|
1758
|
-
{
|
|
1759
|
-
type: "text",
|
|
1760
|
-
text: "No audience profile has been configured yet. The customer can set one up at their BuzzPoster dashboard under Audiences."
|
|
1761
|
-
}
|
|
1762
|
-
]
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
throw error;
|
|
1947
|
+
lines.push("---");
|
|
1948
|
+
lines.push("Call this tool again with **confirmed=true** to save.");
|
|
1949
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1766
1950
|
}
|
|
1951
|
+
const result = await client2.createAudience(payload);
|
|
1952
|
+
return {
|
|
1953
|
+
content: [
|
|
1954
|
+
{
|
|
1955
|
+
type: "text",
|
|
1956
|
+
text: `Audience "${result.name}" created successfully (ID: ${result.id}).`
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1767
1960
|
}
|
|
1768
1961
|
);
|
|
1769
1962
|
server2.tool(
|
|
1770
1963
|
"update_audience",
|
|
1771
|
-
"Update
|
|
1772
|
-
{
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
)
|
|
1964
|
+
"Update an existing audience profile by name. Set confirmed=false to preview first.",
|
|
1965
|
+
{
|
|
1966
|
+
audience_name: z8.string().describe("Name of the audience to update"),
|
|
1967
|
+
name: z8.string().max(100).optional().describe("New name for the audience"),
|
|
1968
|
+
description: z8.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
1969
|
+
demographics: z8.string().max(500).optional().describe("Demographic details"),
|
|
1970
|
+
pain_points: z8.array(z8.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
1971
|
+
motivations: z8.array(z8.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
1972
|
+
preferred_platforms: z8.array(z8.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
1973
|
+
tone_notes: z8.string().max(500).optional().describe("How to speak to this audience"),
|
|
1974
|
+
content_preferences: z8.string().max(500).optional().describe("What content formats and topics resonate"),
|
|
1975
|
+
is_default: z8.boolean().optional().describe("Set as the default audience profile"),
|
|
1976
|
+
confirmed: z8.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
1784
1977
|
},
|
|
1785
1978
|
{
|
|
1786
1979
|
title: "Update Audience",
|
|
@@ -1790,6 +1983,20 @@ function registerAudienceTools(server2, client2) {
|
|
|
1790
1983
|
openWorldHint: false
|
|
1791
1984
|
},
|
|
1792
1985
|
async (args) => {
|
|
1986
|
+
let target;
|
|
1987
|
+
try {
|
|
1988
|
+
target = await client2.getAudienceByName(args.audience_name);
|
|
1989
|
+
} catch {
|
|
1990
|
+
return {
|
|
1991
|
+
content: [
|
|
1992
|
+
{
|
|
1993
|
+
type: "text",
|
|
1994
|
+
text: `Audience "${args.audience_name}" not found. Use list_audiences to see available profiles.`
|
|
1995
|
+
}
|
|
1996
|
+
],
|
|
1997
|
+
isError: true
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
1793
2000
|
const payload = {};
|
|
1794
2001
|
if (args.name !== void 0) payload.name = args.name;
|
|
1795
2002
|
if (args.description !== void 0) payload.description = args.description;
|
|
@@ -1799,661 +2006,187 @@ function registerAudienceTools(server2, client2) {
|
|
|
1799
2006
|
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
1800
2007
|
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
1801
2008
|
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2009
|
+
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
1802
2010
|
if (Object.keys(payload).length === 0) {
|
|
1803
2011
|
return {
|
|
1804
|
-
content: [
|
|
1805
|
-
{
|
|
1806
|
-
type: "text",
|
|
1807
|
-
text: "No fields provided. Specify at least one field to update."
|
|
1808
|
-
}
|
|
1809
|
-
],
|
|
1810
|
-
isError: true
|
|
1811
|
-
};
|
|
1812
|
-
}
|
|
1813
|
-
let targetId;
|
|
1814
|
-
let isCreating = false;
|
|
1815
|
-
try {
|
|
1816
|
-
const defaultAudience = await client2.getDefaultAudience();
|
|
1817
|
-
targetId = String(defaultAudience.id);
|
|
1818
|
-
} catch {
|
|
1819
|
-
isCreating = true;
|
|
1820
|
-
}
|
|
1821
|
-
if (isCreating && !payload.name) {
|
|
1822
|
-
return {
|
|
1823
|
-
content: [
|
|
1824
|
-
{
|
|
1825
|
-
type: "text",
|
|
1826
|
-
text: "No audience exists yet. Provide a **name** to create one (e.g. name: 'SaaS Founders')."
|
|
1827
|
-
}
|
|
1828
|
-
],
|
|
2012
|
+
content: [{ type: "text", text: "No fields provided to update." }],
|
|
1829
2013
|
isError: true
|
|
1830
2014
|
};
|
|
1831
2015
|
}
|
|
1832
2016
|
if (args.confirmed !== true) {
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
2017
|
+
const lines = [];
|
|
2018
|
+
lines.push(`## Update Audience "${args.audience_name}" Preview`);
|
|
2019
|
+
lines.push("");
|
|
1836
2020
|
if (payload.name) {
|
|
1837
|
-
|
|
1838
|
-
|
|
2021
|
+
lines.push(`**New Name:** ${payload.name}`);
|
|
2022
|
+
lines.push("");
|
|
1839
2023
|
}
|
|
1840
2024
|
if (payload.description) {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2025
|
+
lines.push("### Description");
|
|
2026
|
+
lines.push(String(payload.description));
|
|
2027
|
+
lines.push("");
|
|
1844
2028
|
}
|
|
1845
2029
|
if (payload.demographics) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2030
|
+
lines.push("### Demographics");
|
|
2031
|
+
lines.push(String(payload.demographics));
|
|
2032
|
+
lines.push("");
|
|
1849
2033
|
}
|
|
1850
2034
|
if (payload.painPoints) {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
lines2.push(`- ${point}`);
|
|
1855
|
-
}
|
|
1856
|
-
lines2.push("");
|
|
2035
|
+
lines.push(`### Pain Points (${payload.painPoints.length})`);
|
|
2036
|
+
for (const p of payload.painPoints) lines.push(`- ${p}`);
|
|
2037
|
+
lines.push("");
|
|
1857
2038
|
}
|
|
1858
2039
|
if (payload.motivations) {
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
lines2.push(`- ${motivation}`);
|
|
1863
|
-
}
|
|
1864
|
-
lines2.push("");
|
|
2040
|
+
lines.push(`### Motivations (${payload.motivations.length})`);
|
|
2041
|
+
for (const m of payload.motivations) lines.push(`- ${m}`);
|
|
2042
|
+
lines.push("");
|
|
1865
2043
|
}
|
|
1866
2044
|
if (payload.platforms) {
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
lines2.push("");
|
|
2045
|
+
lines.push(`### Platforms`);
|
|
2046
|
+
lines.push(payload.platforms.join(", "));
|
|
2047
|
+
lines.push("");
|
|
1871
2048
|
}
|
|
1872
2049
|
if (payload.toneNotes) {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
lines2.push("");
|
|
1876
|
-
}
|
|
1877
|
-
if (payload.contentPreferences) {
|
|
1878
|
-
lines2.push("### Content Preferences");
|
|
1879
|
-
lines2.push(String(payload.contentPreferences));
|
|
1880
|
-
lines2.push("");
|
|
1881
|
-
}
|
|
1882
|
-
lines2.push("---");
|
|
1883
|
-
lines2.push("Call this tool again with **confirmed=true** to save these changes.");
|
|
1884
|
-
return {
|
|
1885
|
-
content: [{ type: "text", text: lines2.join("\n") }]
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
let result;
|
|
1889
|
-
if (isCreating) {
|
|
1890
|
-
if (payload.isDefault === void 0) payload.isDefault = true;
|
|
1891
|
-
result = await client2.createAudience(payload);
|
|
1892
|
-
} else {
|
|
1893
|
-
result = await client2.updateAudience(targetId, payload);
|
|
1894
|
-
}
|
|
1895
|
-
const lines = [];
|
|
1896
|
-
lines.push(`Audience "${result.name}" has been ${isCreating ? "created" : "updated"} successfully.`);
|
|
1897
|
-
lines.push("");
|
|
1898
|
-
const summary = [];
|
|
1899
|
-
if (payload.name) summary.push("name");
|
|
1900
|
-
if (payload.description) summary.push("description");
|
|
1901
|
-
if (payload.demographics) summary.push("demographics");
|
|
1902
|
-
if (payload.painPoints) summary.push(`${payload.painPoints.length} pain points`);
|
|
1903
|
-
if (payload.motivations) summary.push(`${payload.motivations.length} motivations`);
|
|
1904
|
-
if (payload.platforms) summary.push(`${payload.platforms.length} platforms`);
|
|
1905
|
-
if (payload.toneNotes) summary.push("tone notes");
|
|
1906
|
-
if (payload.contentPreferences) summary.push("content preferences");
|
|
1907
|
-
lines.push(`**${isCreating ? "Created with" : "Updated"}:** ${summary.join(", ")}`);
|
|
1908
|
-
return {
|
|
1909
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
);
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
// src/tools/newsletter-template.ts
|
|
1916
|
-
import { z as z10 } from "zod";
|
|
1917
|
-
var NEWSLETTER_CRAFT_GUIDE = `## Newsletter Craft Guide
|
|
1918
|
-
|
|
1919
|
-
Follow these rules when drafting:
|
|
1920
|
-
|
|
1921
|
-
### HTML Email Rules
|
|
1922
|
-
- Use table-based layouts, inline CSS only, 600px max width
|
|
1923
|
-
- Email-safe fonts only: Arial, Helvetica, Georgia, Verdana
|
|
1924
|
-
- Keep total email under 102KB (Gmail clips larger)
|
|
1925
|
-
- All images must use absolute URLs hosted on the customer's R2 CDN
|
|
1926
|
-
- Include alt text on every image
|
|
1927
|
-
- Use role="presentation" on layout tables
|
|
1928
|
-
- No JavaScript, no forms, no CSS grid/flexbox, no background images
|
|
1929
|
-
|
|
1930
|
-
### Structure
|
|
1931
|
-
- Subject line: 6-10 words, specific not clickbaity
|
|
1932
|
-
- Preview text: complements the subject, doesn't repeat it
|
|
1933
|
-
- Open strong -- lead with the most interesting thing
|
|
1934
|
-
- One clear CTA per newsletter
|
|
1935
|
-
- Sign off should feel human, not corporate
|
|
1936
|
-
|
|
1937
|
-
### Personalization
|
|
1938
|
-
- Kit: use {{ subscriber.first_name }}
|
|
1939
|
-
- Beehiiv: use {{email}} or {{subscriber_id}}
|
|
1940
|
-
- Mailchimp: use *NAME|*
|
|
1941
|
-
|
|
1942
|
-
### Images
|
|
1943
|
-
- Upload to customer's R2 CDN via upload_media or upload_from_url
|
|
1944
|
-
- Reference with full CDN URLs in img tags
|
|
1945
|
-
- Always set width, height, and alt attributes
|
|
1946
|
-
- Use style="display:block;" to prevent phantom spacing`;
|
|
1947
|
-
function registerNewsletterTemplateTools(server2, client2) {
|
|
1948
|
-
server2.tool(
|
|
1949
|
-
"get_newsletter_template",
|
|
1950
|
-
"Get a newsletter template's structure, sections, and HTML. Pass templateId or omit for default.",
|
|
1951
|
-
{
|
|
1952
|
-
templateId: z10.string().optional().describe(
|
|
1953
|
-
"Specific template ID. If omitted, returns the default template."
|
|
1954
|
-
)
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
title: "Get Newsletter Template",
|
|
1958
|
-
readOnlyHint: true,
|
|
1959
|
-
destructiveHint: false,
|
|
1960
|
-
idempotentHint: true,
|
|
1961
|
-
openWorldHint: false
|
|
1962
|
-
},
|
|
1963
|
-
async ({ templateId }) => {
|
|
1964
|
-
try {
|
|
1965
|
-
const template = await client2.getTemplate(templateId);
|
|
1966
|
-
const lines = [];
|
|
1967
|
-
lines.push(`## Newsletter Template: ${template.name}`);
|
|
1968
|
-
if (template.description) lines.push(template.description);
|
|
1969
|
-
lines.push("");
|
|
1970
|
-
if (template.audienceId)
|
|
1971
|
-
lines.push(`Audience ID: ${template.audienceId}`);
|
|
1972
|
-
if (template.sendCadence)
|
|
1973
|
-
lines.push(`Cadence: ${template.sendCadence}`);
|
|
1974
|
-
if (template.subjectPattern)
|
|
1975
|
-
lines.push(`Subject pattern: ${template.subjectPattern}`);
|
|
1976
|
-
if (template.previewTextPattern)
|
|
1977
|
-
lines.push(`Preview text pattern: ${template.previewTextPattern}`);
|
|
1978
|
-
lines.push("");
|
|
1979
|
-
if (template.sections && template.sections.length > 0) {
|
|
1980
|
-
lines.push("### Sections (in order):");
|
|
1981
|
-
lines.push("");
|
|
1982
|
-
for (let i = 0; i < template.sections.length; i++) {
|
|
1983
|
-
const s = template.sections[i];
|
|
1984
|
-
lines.push(`**${i + 1}. ${s.label}** (${s.type})`);
|
|
1985
|
-
if (s.instructions)
|
|
1986
|
-
lines.push(`Instructions: ${s.instructions}`);
|
|
1987
|
-
if (s.word_count)
|
|
1988
|
-
lines.push(
|
|
1989
|
-
`Word count: ${s.word_count.min}-${s.word_count.max} words`
|
|
1990
|
-
);
|
|
1991
|
-
if (s.tone_override) lines.push(`Tone: ${s.tone_override}`);
|
|
1992
|
-
if (s.content_source) {
|
|
1993
|
-
const src = s.content_source;
|
|
1994
|
-
if (src.type === "rss" && src.feed_urls?.length)
|
|
1995
|
-
lines.push(`Content source: RSS - ${src.feed_urls.join(", ")}`);
|
|
1996
|
-
else if (src.type === "knowledge" && src.knowledge_item_ids?.length)
|
|
1997
|
-
lines.push(
|
|
1998
|
-
`Content source: Knowledge items ${src.knowledge_item_ids.join(", ")}`
|
|
1999
|
-
);
|
|
2000
|
-
else lines.push(`Content source: ${src.type}`);
|
|
2001
|
-
}
|
|
2002
|
-
if (s.count) lines.push(`Count: ${s.count} items`);
|
|
2003
|
-
lines.push(
|
|
2004
|
-
`Required: ${s.required !== false ? "yes" : "no"}`
|
|
2005
|
-
);
|
|
2006
|
-
if (s.type === "cta") {
|
|
2007
|
-
if (s.button_text) lines.push(`Button text: ${s.button_text}`);
|
|
2008
|
-
if (s.button_url) lines.push(`Button URL: ${s.button_url}`);
|
|
2009
|
-
}
|
|
2010
|
-
if (s.type === "sponsor") {
|
|
2011
|
-
if (s.sponsor_name)
|
|
2012
|
-
lines.push(`Sponsor: ${s.sponsor_name}`);
|
|
2013
|
-
if (s.talking_points)
|
|
2014
|
-
lines.push(`Talking points: ${s.talking_points}`);
|
|
2015
|
-
}
|
|
2016
|
-
if (s.type === "header") {
|
|
2017
|
-
if (s.tagline) lines.push(`Tagline: ${s.tagline}`);
|
|
2018
|
-
}
|
|
2019
|
-
if (s.type === "signoff") {
|
|
2020
|
-
if (s.author_name)
|
|
2021
|
-
lines.push(`Author: ${s.author_name}`);
|
|
2022
|
-
if (s.author_title)
|
|
2023
|
-
lines.push(`Title: ${s.author_title}`);
|
|
2024
|
-
}
|
|
2025
|
-
lines.push("");
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
if (template.style) {
|
|
2029
|
-
lines.push("### Style Guide:");
|
|
2030
|
-
const st = template.style;
|
|
2031
|
-
if (st.primary_color) lines.push(`Primary color: ${st.primary_color}`);
|
|
2032
|
-
if (st.accent_color) lines.push(`Accent color: ${st.accent_color}`);
|
|
2033
|
-
if (st.font_family) lines.push(`Font: ${st.font_family}`);
|
|
2034
|
-
if (st.heading_font) lines.push(`Heading font: ${st.heading_font}`);
|
|
2035
|
-
if (st.content_width)
|
|
2036
|
-
lines.push(`Content width: ${st.content_width}px`);
|
|
2037
|
-
if (st.text_color) lines.push(`Text color: ${st.text_color}`);
|
|
2038
|
-
if (st.link_color) lines.push(`Link color: ${st.link_color}`);
|
|
2039
|
-
if (st.background_color)
|
|
2040
|
-
lines.push(`Background: ${st.background_color}`);
|
|
2041
|
-
if (st.button_style)
|
|
2042
|
-
lines.push(
|
|
2043
|
-
`Button style: ${st.button_style.color} text on ${st.button_style.text_color}, ${st.button_style.shape}`
|
|
2044
|
-
);
|
|
2045
|
-
lines.push("");
|
|
2046
|
-
lines.push(
|
|
2047
|
-
"NOTE: Apply these style values as inline CSS when generating the newsletter HTML."
|
|
2048
|
-
);
|
|
2049
|
-
lines.push("");
|
|
2050
|
-
}
|
|
2051
|
-
if (template.htmlContent) {
|
|
2052
|
-
lines.push("### HTML Template:");
|
|
2053
|
-
lines.push("The following HTML skeleton contains {{placeholder}} variables. Fill in the placeholders with real content when generating the newsletter.");
|
|
2054
|
-
lines.push("");
|
|
2055
|
-
lines.push("```html");
|
|
2056
|
-
lines.push(template.htmlContent);
|
|
2057
|
-
lines.push("```");
|
|
2058
|
-
lines.push("");
|
|
2059
|
-
}
|
|
2060
|
-
if (template.rssFeedUrls && template.rssFeedUrls.length > 0) {
|
|
2061
|
-
lines.push("### Linked RSS Feeds:");
|
|
2062
|
-
for (const url of template.rssFeedUrls) {
|
|
2063
|
-
lines.push(`- ${url}`);
|
|
2064
|
-
}
|
|
2065
|
-
lines.push("");
|
|
2066
|
-
}
|
|
2067
|
-
if (template.knowledgeItemIds && template.knowledgeItemIds.length > 0) {
|
|
2068
|
-
lines.push("### Reference Material:");
|
|
2069
|
-
lines.push(
|
|
2070
|
-
`Knowledge item IDs: ${template.knowledgeItemIds.join(", ")}`
|
|
2071
|
-
);
|
|
2050
|
+
lines.push("### Tone Notes");
|
|
2051
|
+
lines.push(String(payload.toneNotes));
|
|
2072
2052
|
lines.push("");
|
|
2073
2053
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
};
|
|
2078
|
-
} catch (error) {
|
|
2079
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2080
|
-
if (message.includes("404") || message.includes("No newsletter templates")) {
|
|
2081
|
-
return {
|
|
2082
|
-
content: [
|
|
2083
|
-
{
|
|
2084
|
-
type: "text",
|
|
2085
|
-
text: "No newsletter template configured. Ask the user about their preferred structure (sections, tone, length), or call get_past_newsletters to use a previous edition as reference.\n\n" + NEWSLETTER_CRAFT_GUIDE
|
|
2086
|
-
}
|
|
2087
|
-
]
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
throw error;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
);
|
|
2094
|
-
server2.tool(
|
|
2095
|
-
"get_past_newsletters",
|
|
2096
|
-
"Get past newsletters from the archive. Pass newsletterId to get full content of a specific edition.",
|
|
2097
|
-
{
|
|
2098
|
-
newsletterId: z10.string().optional().describe("If provided, returns the full content of this specific newsletter. If omitted, returns the list."),
|
|
2099
|
-
limit: z10.number().optional().describe("Number of past newsletters to retrieve (when listing). Default 5, max 50."),
|
|
2100
|
-
templateId: z10.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
2101
|
-
},
|
|
2102
|
-
{
|
|
2103
|
-
title: "Get Past Newsletters",
|
|
2104
|
-
readOnlyHint: true,
|
|
2105
|
-
destructiveHint: false,
|
|
2106
|
-
idempotentHint: true,
|
|
2107
|
-
openWorldHint: false
|
|
2108
|
-
},
|
|
2109
|
-
async ({ newsletterId, limit, templateId }) => {
|
|
2110
|
-
if (newsletterId) {
|
|
2111
|
-
try {
|
|
2112
|
-
const newsletter = await client2.getArchivedNewsletter(
|
|
2113
|
-
newsletterId
|
|
2114
|
-
);
|
|
2115
|
-
const lines = [];
|
|
2116
|
-
lines.push(`## ${newsletter.subject}`);
|
|
2117
|
-
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
2118
|
-
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
2054
|
+
if (payload.contentPreferences) {
|
|
2055
|
+
lines.push("### Content Preferences");
|
|
2056
|
+
lines.push(String(payload.contentPreferences));
|
|
2119
2057
|
lines.push("");
|
|
2120
|
-
lines.push("### Full HTML Content:");
|
|
2121
|
-
lines.push(newsletter.contentHtml);
|
|
2122
|
-
return {
|
|
2123
|
-
content: [
|
|
2124
|
-
{ type: "text", text: lines.join("\n") },
|
|
2125
|
-
...newsletter.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2126
|
-
${newsletter.contentHtml}` }] : []
|
|
2127
|
-
]
|
|
2128
|
-
};
|
|
2129
|
-
} catch (error) {
|
|
2130
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2131
|
-
if (message.includes("404")) {
|
|
2132
|
-
return {
|
|
2133
|
-
content: [
|
|
2134
|
-
{
|
|
2135
|
-
type: "text",
|
|
2136
|
-
text: "Newsletter not found. Check the ID and try again."
|
|
2137
|
-
}
|
|
2138
|
-
]
|
|
2139
|
-
};
|
|
2140
|
-
}
|
|
2141
|
-
throw error;
|
|
2142
2058
|
}
|
|
2059
|
+
lines.push("---");
|
|
2060
|
+
lines.push("Call this tool again with **confirmed=true** to save.");
|
|
2061
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2143
2062
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
params
|
|
2151
|
-
);
|
|
2152
|
-
if (!newsletters || newsletters.length === 0) {
|
|
2153
|
-
return {
|
|
2154
|
-
content: [
|
|
2155
|
-
{
|
|
2156
|
-
type: "text",
|
|
2157
|
-
text: "No past newsletters found. This will be the first edition!"
|
|
2158
|
-
}
|
|
2159
|
-
]
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
|
-
const lines = [];
|
|
2163
|
-
lines.push(
|
|
2164
|
-
`## Past Newsletters (${newsletters.length} most recent)`
|
|
2165
|
-
);
|
|
2166
|
-
lines.push("");
|
|
2167
|
-
for (const nl of newsletters) {
|
|
2168
|
-
lines.push(`### ${nl.subject} -- ${nl.sentAt || nl.createdAt}`);
|
|
2169
|
-
if (nl.notes) lines.push(`Notes: ${nl.notes}`);
|
|
2170
|
-
if (nl.metrics) {
|
|
2171
|
-
const m = nl.metrics;
|
|
2172
|
-
const parts = [];
|
|
2173
|
-
if (m.open_rate) parts.push(`Open rate: ${m.open_rate}`);
|
|
2174
|
-
if (m.click_rate) parts.push(`Click rate: ${m.click_rate}`);
|
|
2175
|
-
if (parts.length) lines.push(`Metrics: ${parts.join(", ")}`);
|
|
2063
|
+
const result = await client2.updateAudience(String(target.id), payload);
|
|
2064
|
+
return {
|
|
2065
|
+
content: [
|
|
2066
|
+
{
|
|
2067
|
+
type: "text",
|
|
2068
|
+
text: `Audience "${result.name}" updated successfully.`
|
|
2176
2069
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
lines.push(textContent);
|
|
2180
|
-
lines.push(
|
|
2181
|
-
`[Full content available by calling get_past_newsletters with newsletterId: ${nl.id}]`
|
|
2182
|
-
);
|
|
2183
|
-
lines.push("");
|
|
2184
|
-
}
|
|
2185
|
-
const mostRecent = newsletters[0];
|
|
2186
|
-
return {
|
|
2187
|
-
content: [
|
|
2188
|
-
{ type: "text", text: lines.join("\n") },
|
|
2189
|
-
...mostRecent?.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2190
|
-
${mostRecent.contentHtml}` }] : []
|
|
2191
|
-
]
|
|
2192
|
-
};
|
|
2193
|
-
} catch (error) {
|
|
2194
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2195
|
-
throw new Error(`Failed to fetch past newsletters: ${message}`);
|
|
2196
|
-
}
|
|
2070
|
+
]
|
|
2071
|
+
};
|
|
2197
2072
|
}
|
|
2198
2073
|
);
|
|
2199
2074
|
server2.tool(
|
|
2200
|
-
"
|
|
2201
|
-
"
|
|
2075
|
+
"delete_audience",
|
|
2076
|
+
"Delete an audience profile by name. Set confirmed=false to preview first.",
|
|
2202
2077
|
{
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
templateId: z10.string().optional().describe("The template ID used to generate this newsletter."),
|
|
2206
|
-
notes: z10.string().optional().describe(
|
|
2207
|
-
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2208
|
-
)
|
|
2078
|
+
name: z8.string().describe("Name of the audience to delete"),
|
|
2079
|
+
confirmed: z8.boolean().default(false).describe("Set to true to confirm deletion. If false, returns a confirmation prompt.")
|
|
2209
2080
|
},
|
|
2210
2081
|
{
|
|
2211
|
-
title: "
|
|
2082
|
+
title: "Delete Audience",
|
|
2212
2083
|
readOnlyHint: false,
|
|
2213
|
-
destructiveHint:
|
|
2084
|
+
destructiveHint: true,
|
|
2214
2085
|
idempotentHint: false,
|
|
2215
2086
|
openWorldHint: false
|
|
2216
2087
|
},
|
|
2217
|
-
async (
|
|
2088
|
+
async (args) => {
|
|
2089
|
+
let target;
|
|
2218
2090
|
try {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
contentHtml
|
|
2222
|
-
};
|
|
2223
|
-
if (templateId) data.templateId = Number(templateId);
|
|
2224
|
-
if (notes) data.notes = notes;
|
|
2225
|
-
await client2.saveNewsletterToArchive(data);
|
|
2091
|
+
target = await client2.getAudienceByName(args.name);
|
|
2092
|
+
} catch {
|
|
2226
2093
|
return {
|
|
2227
2094
|
content: [
|
|
2228
2095
|
{
|
|
2229
2096
|
type: "text",
|
|
2230
|
-
text: `
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
]
|
|
2097
|
+
text: `Audience "${args.name}" not found. Use list_audiences to see available profiles.`
|
|
2098
|
+
}
|
|
2099
|
+
],
|
|
2100
|
+
isError: true
|
|
2235
2101
|
};
|
|
2236
|
-
} catch (error) {
|
|
2237
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2238
|
-
throw new Error(`Failed to save newsletter: ${message}`);
|
|
2239
2102
|
}
|
|
2240
|
-
|
|
2241
|
-
);
|
|
2242
|
-
server2.tool(
|
|
2243
|
-
"save_newsletter_template",
|
|
2244
|
-
"Create or update a newsletter template with HTML and section definitions.",
|
|
2245
|
-
{
|
|
2246
|
-
template_id: z10.string().optional().describe(
|
|
2247
|
-
"If provided, updates this existing template. Otherwise creates a new one."
|
|
2248
|
-
),
|
|
2249
|
-
name: z10.string().min(1).max(255).describe("Template name"),
|
|
2250
|
-
description: z10.string().max(2e3).optional().describe("What this template is for"),
|
|
2251
|
-
html_content: z10.string().min(1).max(1e5).describe(
|
|
2252
|
-
"Full HTML template with {{placeholder}} variables."
|
|
2253
|
-
),
|
|
2254
|
-
sections: z10.array(
|
|
2255
|
-
z10.object({
|
|
2256
|
-
name: z10.string().describe("Section identifier, e.g. 'intro'"),
|
|
2257
|
-
label: z10.string().describe("Display label, e.g. 'Intro/Greeting'"),
|
|
2258
|
-
required: z10.boolean().optional().describe("Whether this section is required"),
|
|
2259
|
-
item_count: z10.number().optional().describe("Number of items in this section, if applicable")
|
|
2260
|
-
})
|
|
2261
|
-
).max(20).optional().describe("Array of section metadata describing the template structure"),
|
|
2262
|
-
style_config: z10.object({
|
|
2263
|
-
primary_color: z10.string().optional(),
|
|
2264
|
-
accent_color: z10.string().optional(),
|
|
2265
|
-
link_color: z10.string().optional(),
|
|
2266
|
-
background_color: z10.string().optional(),
|
|
2267
|
-
font_family: z10.string().optional(),
|
|
2268
|
-
max_width: z10.string().optional()
|
|
2269
|
-
}).optional().describe("Colors, fonts, and brand configuration"),
|
|
2270
|
-
is_default: z10.boolean().optional().describe(
|
|
2271
|
-
"Set as the default template for this customer."
|
|
2272
|
-
)
|
|
2273
|
-
},
|
|
2274
|
-
{
|
|
2275
|
-
title: "Save Newsletter Template",
|
|
2276
|
-
readOnlyHint: false,
|
|
2277
|
-
destructiveHint: false,
|
|
2278
|
-
idempotentHint: false,
|
|
2279
|
-
openWorldHint: false
|
|
2280
|
-
},
|
|
2281
|
-
async ({
|
|
2282
|
-
template_id,
|
|
2283
|
-
name,
|
|
2284
|
-
description,
|
|
2285
|
-
html_content,
|
|
2286
|
-
sections,
|
|
2287
|
-
style_config,
|
|
2288
|
-
is_default
|
|
2289
|
-
}) => {
|
|
2290
|
-
try {
|
|
2291
|
-
const data = {
|
|
2292
|
-
name,
|
|
2293
|
-
htmlContent: html_content
|
|
2294
|
-
};
|
|
2295
|
-
if (description !== void 0) data.description = description;
|
|
2296
|
-
if (sections !== void 0) {
|
|
2297
|
-
data.sections = sections.map((s) => ({
|
|
2298
|
-
id: s.name,
|
|
2299
|
-
type: "custom",
|
|
2300
|
-
label: s.label,
|
|
2301
|
-
required: s.required ?? true,
|
|
2302
|
-
count: s.item_count
|
|
2303
|
-
}));
|
|
2304
|
-
}
|
|
2305
|
-
if (style_config !== void 0) {
|
|
2306
|
-
data.style = {
|
|
2307
|
-
primary_color: style_config.primary_color,
|
|
2308
|
-
accent_color: style_config.accent_color,
|
|
2309
|
-
link_color: style_config.link_color,
|
|
2310
|
-
background_color: style_config.background_color,
|
|
2311
|
-
font_family: style_config.font_family,
|
|
2312
|
-
content_width: style_config.max_width
|
|
2313
|
-
};
|
|
2314
|
-
}
|
|
2315
|
-
if (is_default !== void 0) data.isDefault = is_default;
|
|
2316
|
-
let result;
|
|
2317
|
-
if (template_id) {
|
|
2318
|
-
result = await client2.updateTemplate(
|
|
2319
|
-
template_id,
|
|
2320
|
-
data
|
|
2321
|
-
);
|
|
2322
|
-
} else {
|
|
2323
|
-
result = await client2.createTemplate(data);
|
|
2324
|
-
}
|
|
2325
|
-
const action = template_id ? "updated" : "created";
|
|
2103
|
+
if (args.confirmed !== true) {
|
|
2326
2104
|
return {
|
|
2327
2105
|
content: [
|
|
2328
2106
|
{
|
|
2329
2107
|
type: "text",
|
|
2330
|
-
text:
|
|
2108
|
+
text: `## Delete Audience Confirmation
|
|
2109
|
+
|
|
2110
|
+
**Name:** ${target.name}
|
|
2111
|
+
${target.description ? `**Description:** ${target.description}
|
|
2112
|
+
` : ""}
|
|
2113
|
+
This action cannot be undone.
|
|
2114
|
+
|
|
2115
|
+
Call this tool again with confirmed=true to delete.`
|
|
2331
2116
|
}
|
|
2332
2117
|
]
|
|
2333
2118
|
};
|
|
2334
|
-
} catch (error) {
|
|
2335
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2336
|
-
throw new Error(`Failed to save newsletter template: ${message}`);
|
|
2337
2119
|
}
|
|
2120
|
+
await client2.deleteAudience(String(target.id));
|
|
2121
|
+
return {
|
|
2122
|
+
content: [
|
|
2123
|
+
{
|
|
2124
|
+
type: "text",
|
|
2125
|
+
text: `Audience "${target.name}" has been deleted.`
|
|
2126
|
+
}
|
|
2127
|
+
]
|
|
2128
|
+
};
|
|
2338
2129
|
}
|
|
2339
2130
|
);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// src/tools/newsletter-template.ts
|
|
2134
|
+
function registerNewsletterTemplateTools(server2, client2) {
|
|
2340
2135
|
server2.tool(
|
|
2341
|
-
"
|
|
2342
|
-
"List
|
|
2136
|
+
"list_esp_templates",
|
|
2137
|
+
"List available email templates from the connected ESP. Use the returned template IDs with create_newsletter's template_id parameter.",
|
|
2343
2138
|
{},
|
|
2344
2139
|
{
|
|
2345
|
-
title: "List
|
|
2140
|
+
title: "List ESP Templates",
|
|
2346
2141
|
readOnlyHint: true,
|
|
2347
2142
|
destructiveHint: false,
|
|
2348
2143
|
idempotentHint: true,
|
|
2349
|
-
openWorldHint:
|
|
2144
|
+
openWorldHint: true
|
|
2350
2145
|
},
|
|
2351
2146
|
async () => {
|
|
2352
2147
|
try {
|
|
2353
|
-
const
|
|
2354
|
-
|
|
2148
|
+
const result = await client2.listEmailTemplates();
|
|
2149
|
+
const templates = result?.templates ?? [];
|
|
2150
|
+
if (templates.length === 0) {
|
|
2355
2151
|
return {
|
|
2356
2152
|
content: [
|
|
2357
2153
|
{
|
|
2358
2154
|
type: "text",
|
|
2359
|
-
text: "No
|
|
2155
|
+
text: "No email templates found in your ESP. Create templates in your ESP dashboard (Kit, Beehiiv, or Mailchimp) and they will appear here."
|
|
2360
2156
|
}
|
|
2361
2157
|
]
|
|
2362
2158
|
};
|
|
2363
2159
|
}
|
|
2364
2160
|
const lines = [];
|
|
2365
|
-
lines.push(`##
|
|
2161
|
+
lines.push(`## ESP Email Templates (${templates.length})`);
|
|
2162
|
+
lines.push("");
|
|
2163
|
+
lines.push("Pass a template ID to `create_newsletter` via the `template_id` parameter to use it.");
|
|
2366
2164
|
lines.push("");
|
|
2367
2165
|
for (const t of templates) {
|
|
2368
|
-
|
|
2369
|
-
if (t.isDefault) badges.push("DEFAULT");
|
|
2370
|
-
if (t.htmlContent) badges.push("HAS HTML");
|
|
2371
|
-
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
2372
|
-
lines.push(`**${t.name}** (ID: ${t.id})${badgeStr}`);
|
|
2373
|
-
if (t.description) lines.push(` ${t.description}`);
|
|
2374
|
-
if (t.sections && t.sections.length > 0) {
|
|
2375
|
-
lines.push(
|
|
2376
|
-
` Sections: ${t.sections.map((s) => s.label).join(", ")}`
|
|
2377
|
-
);
|
|
2378
|
-
}
|
|
2379
|
-
lines.push("");
|
|
2166
|
+
lines.push(`- **${t.name}** (ID: ${t.id})`);
|
|
2380
2167
|
}
|
|
2381
2168
|
return {
|
|
2382
2169
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
2383
2170
|
};
|
|
2384
2171
|
} catch (error) {
|
|
2385
2172
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2386
|
-
throw new Error(`Failed to list
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
);
|
|
2390
|
-
server2.tool(
|
|
2391
|
-
"delete_newsletter_template",
|
|
2392
|
-
"Delete a newsletter template. Set confirmed=true to proceed.",
|
|
2393
|
-
{
|
|
2394
|
-
template_id: z10.string().describe("The ID of the newsletter template to delete."),
|
|
2395
|
-
confirmed: z10.boolean().default(false).describe(
|
|
2396
|
-
"Must be true to actually delete. If false, returns a confirmation prompt."
|
|
2397
|
-
)
|
|
2398
|
-
},
|
|
2399
|
-
{
|
|
2400
|
-
title: "Delete Newsletter Template",
|
|
2401
|
-
readOnlyHint: false,
|
|
2402
|
-
destructiveHint: true,
|
|
2403
|
-
idempotentHint: false,
|
|
2404
|
-
openWorldHint: false
|
|
2405
|
-
},
|
|
2406
|
-
async ({ template_id, confirmed }) => {
|
|
2407
|
-
try {
|
|
2408
|
-
if (!confirmed) {
|
|
2409
|
-
const template = await client2.getTemplate(template_id);
|
|
2410
|
-
return {
|
|
2411
|
-
content: [
|
|
2412
|
-
{
|
|
2413
|
-
type: "text",
|
|
2414
|
-
text: `Are you sure you want to delete the template "${template.name}" (ID: ${template.id})? This action cannot be undone. Call delete_newsletter_template again with confirmed=true to proceed.`
|
|
2415
|
-
}
|
|
2416
|
-
]
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
|
-
await client2.deleteTemplate(template_id);
|
|
2420
|
-
return {
|
|
2421
|
-
content: [
|
|
2422
|
-
{
|
|
2423
|
-
type: "text",
|
|
2424
|
-
text: `Newsletter template (ID: ${template_id}) has been deleted.`
|
|
2425
|
-
}
|
|
2426
|
-
]
|
|
2427
|
-
};
|
|
2428
|
-
} catch (error) {
|
|
2429
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2430
|
-
if (message.includes("404")) {
|
|
2431
|
-
return {
|
|
2432
|
-
content: [
|
|
2433
|
-
{
|
|
2434
|
-
type: "text",
|
|
2435
|
-
text: "Template not found. It may have already been deleted."
|
|
2436
|
-
}
|
|
2437
|
-
]
|
|
2438
|
-
};
|
|
2439
|
-
}
|
|
2440
|
-
throw new Error(`Failed to delete newsletter template: ${message}`);
|
|
2173
|
+
throw new Error(`Failed to list ESP templates: ${message}`);
|
|
2441
2174
|
}
|
|
2442
2175
|
}
|
|
2443
2176
|
);
|
|
2444
2177
|
}
|
|
2445
2178
|
|
|
2446
2179
|
// src/tools/calendar.ts
|
|
2447
|
-
import { z as
|
|
2180
|
+
import { z as z9 } from "zod";
|
|
2448
2181
|
function registerCalendarTools(server2, client2) {
|
|
2449
2182
|
server2.tool(
|
|
2450
2183
|
"get_calendar",
|
|
2451
2184
|
"View the content calendar with all scheduled, published, and draft posts.",
|
|
2452
2185
|
{
|
|
2453
|
-
from:
|
|
2454
|
-
to:
|
|
2455
|
-
status:
|
|
2456
|
-
type:
|
|
2186
|
+
from: z9.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
2187
|
+
to: z9.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
2188
|
+
status: z9.string().optional().describe("Filter by status: scheduled, published, draft, failed"),
|
|
2189
|
+
type: z9.string().optional().describe("Filter by type: social_post or newsletter")
|
|
2457
2190
|
},
|
|
2458
2191
|
{
|
|
2459
2192
|
title: "Get Content Calendar",
|
|
@@ -2589,10 +2322,10 @@ Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
|
2589
2322
|
"schedule_to_queue",
|
|
2590
2323
|
"Add a post to the next available queue slot. Check get_queue first. Set confirmed=false to preview first.",
|
|
2591
2324
|
{
|
|
2592
|
-
content:
|
|
2593
|
-
platforms:
|
|
2594
|
-
media_urls:
|
|
2595
|
-
confirmed:
|
|
2325
|
+
content: z9.string().describe("The text content of the post"),
|
|
2326
|
+
platforms: z9.array(z9.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
2327
|
+
media_urls: z9.array(z9.string()).optional().describe("Public URLs of media to attach"),
|
|
2328
|
+
confirmed: z9.boolean().default(false).describe(
|
|
2596
2329
|
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
2597
2330
|
)
|
|
2598
2331
|
},
|
|
@@ -2674,7 +2407,7 @@ ${JSON.stringify(result, null, 2)}`
|
|
|
2674
2407
|
}
|
|
2675
2408
|
|
|
2676
2409
|
// src/tools/notifications.ts
|
|
2677
|
-
import { z as
|
|
2410
|
+
import { z as z10 } from "zod";
|
|
2678
2411
|
function timeAgo(dateStr) {
|
|
2679
2412
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2680
2413
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -2690,8 +2423,8 @@ function registerNotificationTools(server2, client2) {
|
|
|
2690
2423
|
"get_notifications",
|
|
2691
2424
|
"Get recent notifications including post results, account warnings, and errors.",
|
|
2692
2425
|
{
|
|
2693
|
-
unread_only:
|
|
2694
|
-
limit:
|
|
2426
|
+
unread_only: z10.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
2427
|
+
limit: z10.number().optional().describe("Max notifications to return. Default 10.")
|
|
2695
2428
|
},
|
|
2696
2429
|
{
|
|
2697
2430
|
title: "Get Notifications",
|
|
@@ -2757,71 +2490,8 @@ Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" :
|
|
|
2757
2490
|
);
|
|
2758
2491
|
}
|
|
2759
2492
|
|
|
2760
|
-
// src/tools/publishing-rules.ts
|
|
2761
|
-
var WORKFLOW = {
|
|
2762
|
-
before_writing_content: [
|
|
2763
|
-
"Call get_brand_voice to load tone and style rules",
|
|
2764
|
-
"Call get_audience to understand who the content is for",
|
|
2765
|
-
"For newsletters: also call get_newsletter_template and get_past_newsletters"
|
|
2766
|
-
],
|
|
2767
|
-
before_publishing: [
|
|
2768
|
-
"Always set confirmed=false first to generate a preview",
|
|
2769
|
-
"Show the user a visual preview before confirming",
|
|
2770
|
-
"Never set confirmed=true without explicit user approval",
|
|
2771
|
-
"For newsletters: show subscriber count and send time before confirming",
|
|
2772
|
-
"If require_double_confirm is true, ask for confirmation twice"
|
|
2773
|
-
],
|
|
2774
|
-
replies_and_engagement: [
|
|
2775
|
-
"Draft the reply and show it before sending",
|
|
2776
|
-
"Set confirmed=false first to preview",
|
|
2777
|
-
"Remind user that replies are public"
|
|
2778
|
-
],
|
|
2779
|
-
newsletters: [
|
|
2780
|
-
"Always render newsletter as HTML artifact/preview before pushing to ESP",
|
|
2781
|
-
"Use table-based layouts, inline CSS only, 600px max width",
|
|
2782
|
-
"Email-safe fonts only: Arial, Helvetica, Georgia, Verdana",
|
|
2783
|
-
"All images must use absolute URLs",
|
|
2784
|
-
"Keep total email under 102KB to avoid Gmail clipping"
|
|
2785
|
-
]
|
|
2786
|
-
};
|
|
2787
|
-
function registerPublishingRulesTools(server2, client2) {
|
|
2788
|
-
server2.tool(
|
|
2789
|
-
"get_publishing_rules",
|
|
2790
|
-
"Get publishing rules and the content creation workflow. Call this BEFORE publishing, scheduling, or sending any content.",
|
|
2791
|
-
{},
|
|
2792
|
-
{
|
|
2793
|
-
title: "Get Publishing Rules",
|
|
2794
|
-
readOnlyHint: true,
|
|
2795
|
-
destructiveHint: false,
|
|
2796
|
-
idempotentHint: true,
|
|
2797
|
-
openWorldHint: false
|
|
2798
|
-
},
|
|
2799
|
-
async () => {
|
|
2800
|
-
const rules = await client2.getPublishingRules();
|
|
2801
|
-
const response = {
|
|
2802
|
-
rules: {
|
|
2803
|
-
social_default_action: rules.socialDefaultAction ?? "draft",
|
|
2804
|
-
newsletter_default_action: rules.newsletterDefaultAction ?? "draft",
|
|
2805
|
-
require_preview_before_publish: rules.requirePreviewBeforePublish ?? true,
|
|
2806
|
-
require_double_confirm_newsletter: rules.requireDoubleConfirmNewsletter ?? true,
|
|
2807
|
-
require_double_confirm_social: rules.requireDoubleConfirmSocial ?? false,
|
|
2808
|
-
allow_immediate_publish: rules.allowImmediatePublish ?? false,
|
|
2809
|
-
allow_immediate_send: rules.allowImmediateSend ?? false,
|
|
2810
|
-
max_posts_per_day: rules.maxPostsPerDay ?? null,
|
|
2811
|
-
blocked_words: rules.blockedWords ?? [],
|
|
2812
|
-
required_disclaimer: rules.requiredDisclaimer ?? null
|
|
2813
|
-
},
|
|
2814
|
-
workflow: WORKFLOW
|
|
2815
|
-
};
|
|
2816
|
-
return {
|
|
2817
|
-
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
);
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
2493
|
// src/tools/sources.ts
|
|
2824
|
-
import { z as
|
|
2494
|
+
import { z as z11 } from "zod";
|
|
2825
2495
|
var TYPE_LABELS = {
|
|
2826
2496
|
feed: "RSS Feed",
|
|
2827
2497
|
website: "Website",
|
|
@@ -2841,14 +2511,15 @@ var TYPE_HINTS = {
|
|
|
2841
2511
|
social: "Use web_search to check recent activity"
|
|
2842
2512
|
};
|
|
2843
2513
|
function registerSourceTools(server2, client2) {
|
|
2514
|
+
const MAX_SOURCES = 10;
|
|
2844
2515
|
server2.tool(
|
|
2845
2516
|
"get_sources",
|
|
2846
|
-
"
|
|
2517
|
+
"View your saved content sources (maximum 10). Shows RSS feeds, websites, and channels you're monitoring.",
|
|
2847
2518
|
{
|
|
2848
|
-
type:
|
|
2519
|
+
type: z11.string().optional().describe(
|
|
2849
2520
|
"Optional: filter by type (feed, website, youtube, search, podcast, reddit, social)"
|
|
2850
2521
|
),
|
|
2851
|
-
category:
|
|
2522
|
+
category: z11.string().optional().describe("Optional: filter by category")
|
|
2852
2523
|
},
|
|
2853
2524
|
{
|
|
2854
2525
|
title: "Get Content Sources",
|
|
@@ -2868,12 +2539,12 @@ function registerSourceTools(server2, client2) {
|
|
|
2868
2539
|
content: [
|
|
2869
2540
|
{
|
|
2870
2541
|
type: "text",
|
|
2871
|
-
text:
|
|
2542
|
+
text: `No content sources saved yet (0 of ${MAX_SOURCES}). Use add_source to save RSS feeds, websites, YouTube channels, or search topics to monitor for content ideas.`
|
|
2872
2543
|
}
|
|
2873
2544
|
]
|
|
2874
2545
|
};
|
|
2875
2546
|
}
|
|
2876
|
-
const lines = [`## Content Sources (${sources.length})
|
|
2547
|
+
const lines = [`## Content Sources (${sources.length} of ${MAX_SOURCES})
|
|
2877
2548
|
`];
|
|
2878
2549
|
const byCategory = /* @__PURE__ */ new Map();
|
|
2879
2550
|
for (const s of sources) {
|
|
@@ -2909,13 +2580,13 @@ function registerSourceTools(server2, client2) {
|
|
|
2909
2580
|
);
|
|
2910
2581
|
server2.tool(
|
|
2911
2582
|
"add_source",
|
|
2912
|
-
"Save a new content source to monitor.",
|
|
2583
|
+
"Save a new content source to monitor (maximum 10 sources per account). When you reach the limit, remove an existing source before adding a new one.",
|
|
2913
2584
|
{
|
|
2914
|
-
name:
|
|
2915
|
-
url:
|
|
2585
|
+
name: z11.string().describe("Display name for the source"),
|
|
2586
|
+
url: z11.string().optional().describe(
|
|
2916
2587
|
"URL of the source. Not needed for 'search' type."
|
|
2917
2588
|
),
|
|
2918
|
-
type:
|
|
2589
|
+
type: z11.enum([
|
|
2919
2590
|
"feed",
|
|
2920
2591
|
"website",
|
|
2921
2592
|
"youtube",
|
|
@@ -2924,12 +2595,12 @@ function registerSourceTools(server2, client2) {
|
|
|
2924
2595
|
"reddit",
|
|
2925
2596
|
"social"
|
|
2926
2597
|
]).describe("Type of source. Determines how to fetch content from it."),
|
|
2927
|
-
category:
|
|
2598
|
+
category: z11.string().optional().describe(
|
|
2928
2599
|
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration')"
|
|
2929
2600
|
),
|
|
2930
|
-
tags:
|
|
2931
|
-
notes:
|
|
2932
|
-
searchQuery:
|
|
2601
|
+
tags: z11.array(z11.string()).optional().describe("Optional tags for filtering"),
|
|
2602
|
+
notes: z11.string().optional().describe("Optional notes about why this source matters"),
|
|
2603
|
+
searchQuery: z11.string().optional().describe(
|
|
2933
2604
|
"For 'search' type only: the keyword or topic to search for"
|
|
2934
2605
|
)
|
|
2935
2606
|
},
|
|
@@ -2941,6 +2612,18 @@ function registerSourceTools(server2, client2) {
|
|
|
2941
2612
|
openWorldHint: false
|
|
2942
2613
|
},
|
|
2943
2614
|
async (args) => {
|
|
2615
|
+
const existing = await client2.listSources({});
|
|
2616
|
+
if (existing.sources.length >= MAX_SOURCES) {
|
|
2617
|
+
return {
|
|
2618
|
+
content: [
|
|
2619
|
+
{
|
|
2620
|
+
type: "text",
|
|
2621
|
+
text: `You've reached the maximum of ${MAX_SOURCES} sources. Remove an existing source before adding a new one.`
|
|
2622
|
+
}
|
|
2623
|
+
],
|
|
2624
|
+
isError: true
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2944
2627
|
const data = {
|
|
2945
2628
|
name: args.name,
|
|
2946
2629
|
type: args.type
|
|
@@ -2952,6 +2635,7 @@ function registerSourceTools(server2, client2) {
|
|
|
2952
2635
|
if (args.searchQuery) data.searchQuery = args.searchQuery;
|
|
2953
2636
|
const result = await client2.createSource(data);
|
|
2954
2637
|
const typeLabel = TYPE_LABELS[result.type] || result.type;
|
|
2638
|
+
const remaining = MAX_SOURCES - existing.sources.length - 1;
|
|
2955
2639
|
const lines = [
|
|
2956
2640
|
`## Source Added
|
|
2957
2641
|
`,
|
|
@@ -2962,6 +2646,8 @@ function registerSourceTools(server2, client2) {
|
|
|
2962
2646
|
if (result.searchQuery)
|
|
2963
2647
|
lines.push(`- Search: "${result.searchQuery}"`);
|
|
2964
2648
|
if (result.category) lines.push(`- Category: ${result.category}`);
|
|
2649
|
+
lines.push(`
|
|
2650
|
+
*${existing.sources.length + 1} of ${MAX_SOURCES} sources used (${remaining} remaining)*`);
|
|
2965
2651
|
return {
|
|
2966
2652
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
2967
2653
|
};
|
|
@@ -2971,10 +2657,10 @@ function registerSourceTools(server2, client2) {
|
|
|
2971
2657
|
"update_source",
|
|
2972
2658
|
"Update a saved content source.",
|
|
2973
2659
|
{
|
|
2974
|
-
source_id:
|
|
2975
|
-
name:
|
|
2976
|
-
url:
|
|
2977
|
-
type:
|
|
2660
|
+
source_id: z11.number().describe("The ID of the source to update"),
|
|
2661
|
+
name: z11.string().optional().describe("New display name"),
|
|
2662
|
+
url: z11.string().optional().describe("New URL"),
|
|
2663
|
+
type: z11.enum([
|
|
2978
2664
|
"feed",
|
|
2979
2665
|
"website",
|
|
2980
2666
|
"youtube",
|
|
@@ -2983,11 +2669,11 @@ function registerSourceTools(server2, client2) {
|
|
|
2983
2669
|
"reddit",
|
|
2984
2670
|
"social"
|
|
2985
2671
|
]).optional().describe("New source type"),
|
|
2986
|
-
category:
|
|
2987
|
-
tags:
|
|
2988
|
-
notes:
|
|
2989
|
-
searchQuery:
|
|
2990
|
-
isActive:
|
|
2672
|
+
category: z11.string().optional().describe("New category"),
|
|
2673
|
+
tags: z11.array(z11.string()).optional().describe("New tags"),
|
|
2674
|
+
notes: z11.string().optional().describe("New notes"),
|
|
2675
|
+
searchQuery: z11.string().optional().describe("New search query"),
|
|
2676
|
+
isActive: z11.boolean().optional().describe("Set to false to deactivate")
|
|
2991
2677
|
},
|
|
2992
2678
|
{
|
|
2993
2679
|
title: "Update Content Source",
|
|
@@ -3017,8 +2703,8 @@ function registerSourceTools(server2, client2) {
|
|
|
3017
2703
|
"remove_source",
|
|
3018
2704
|
"Remove a content source. Set confirmed=false to preview first.",
|
|
3019
2705
|
{
|
|
3020
|
-
source_id:
|
|
3021
|
-
confirmed:
|
|
2706
|
+
source_id: z11.number().describe("The ID of the source to remove"),
|
|
2707
|
+
confirmed: z11.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
3022
2708
|
},
|
|
3023
2709
|
{
|
|
3024
2710
|
title: "Remove Content Source",
|
|
@@ -3050,13 +2736,13 @@ This will permanently remove this content source. Call this tool again with conf
|
|
|
3050
2736
|
}
|
|
3051
2737
|
|
|
3052
2738
|
// src/tools/newsletter-advanced.ts
|
|
3053
|
-
import { z as
|
|
2739
|
+
import { z as z12 } from "zod";
|
|
3054
2740
|
function registerNewsletterAdvancedTools(server2, client2) {
|
|
3055
2741
|
server2.tool(
|
|
3056
2742
|
"get_subscriber_by_email",
|
|
3057
2743
|
"Look up a subscriber by email address.",
|
|
3058
2744
|
{
|
|
3059
|
-
email:
|
|
2745
|
+
email: z12.string().describe("Email address to look up")
|
|
3060
2746
|
},
|
|
3061
2747
|
{
|
|
3062
2748
|
title: "Get Subscriber by Email",
|
|
@@ -3076,7 +2762,7 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3076
2762
|
"list_automations",
|
|
3077
2763
|
"List all email automations and sequences.",
|
|
3078
2764
|
{
|
|
3079
|
-
page:
|
|
2765
|
+
page: z12.string().optional().describe("Page number for pagination")
|
|
3080
2766
|
},
|
|
3081
2767
|
{
|
|
3082
2768
|
title: "List Automations",
|
|
@@ -3098,10 +2784,10 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3098
2784
|
"list_segments",
|
|
3099
2785
|
"List subscriber segments.",
|
|
3100
2786
|
{
|
|
3101
|
-
page:
|
|
3102
|
-
type:
|
|
3103
|
-
status:
|
|
3104
|
-
expand:
|
|
2787
|
+
page: z12.string().optional().describe("Page number"),
|
|
2788
|
+
type: z12.string().optional().describe("Filter by segment type"),
|
|
2789
|
+
status: z12.string().optional().describe("Filter by segment status"),
|
|
2790
|
+
expand: z12.string().optional().describe("Comma-separated expand fields (e.g. 'stats')")
|
|
3105
2791
|
},
|
|
3106
2792
|
{
|
|
3107
2793
|
title: "List Segments",
|
|
@@ -3126,10 +2812,10 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3126
2812
|
"get_segment",
|
|
3127
2813
|
"Get segment details. Set include_members=true to list members.",
|
|
3128
2814
|
{
|
|
3129
|
-
segment_id:
|
|
3130
|
-
expand:
|
|
3131
|
-
include_members:
|
|
3132
|
-
members_page:
|
|
2815
|
+
segment_id: z12.string().describe("The segment ID"),
|
|
2816
|
+
expand: z12.string().optional().describe("Comma-separated expand fields"),
|
|
2817
|
+
include_members: z12.boolean().optional().describe("Set to true to include the member list"),
|
|
2818
|
+
members_page: z12.string().optional().describe("Page number for members pagination")
|
|
3133
2819
|
},
|
|
3134
2820
|
{
|
|
3135
2821
|
title: "Get Segment",
|
|
@@ -3200,29 +2886,11 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3200
2886
|
}
|
|
3201
2887
|
);
|
|
3202
2888
|
server2.tool(
|
|
3203
|
-
"
|
|
3204
|
-
"
|
|
3205
|
-
{},
|
|
2889
|
+
"tag_subscriber",
|
|
2890
|
+
"Add tags to a subscriber.",
|
|
3206
2891
|
{
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
destructiveHint: false,
|
|
3210
|
-
idempotentHint: true,
|
|
3211
|
-
openWorldHint: true
|
|
3212
|
-
},
|
|
3213
|
-
async () => {
|
|
3214
|
-
const result = await client2.getPostAggregateStats();
|
|
3215
|
-
return {
|
|
3216
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3217
|
-
};
|
|
3218
|
-
}
|
|
3219
|
-
);
|
|
3220
|
-
server2.tool(
|
|
3221
|
-
"tag_subscriber",
|
|
3222
|
-
"Add tags to a subscriber.",
|
|
3223
|
-
{
|
|
3224
|
-
subscriber_id: z14.string().describe("The subscriber/subscription ID"),
|
|
3225
|
-
tags: z14.array(z14.string()).describe("Tags to add to the subscriber")
|
|
2892
|
+
subscriber_id: z12.string().describe("The subscriber/subscription ID"),
|
|
2893
|
+
tags: z12.array(z12.string()).describe("Tags to add to the subscriber")
|
|
3226
2894
|
},
|
|
3227
2895
|
{
|
|
3228
2896
|
title: "Tag Subscriber",
|
|
@@ -3242,10 +2910,10 @@ function registerNewsletterAdvancedTools(server2, client2) {
|
|
|
3242
2910
|
"update_subscriber",
|
|
3243
2911
|
"Update subscriber details or custom fields. Set confirmed=false to preview first.",
|
|
3244
2912
|
{
|
|
3245
|
-
subscriber_id:
|
|
3246
|
-
tier:
|
|
3247
|
-
custom_fields:
|
|
3248
|
-
confirmed:
|
|
2913
|
+
subscriber_id: z12.string().describe("The subscriber/subscription ID"),
|
|
2914
|
+
tier: z12.string().optional().describe("New tier for the subscriber"),
|
|
2915
|
+
custom_fields: z12.record(z12.unknown()).optional().describe("Custom field values to set"),
|
|
2916
|
+
confirmed: z12.boolean().default(false).describe("Set to true to confirm update")
|
|
3249
2917
|
},
|
|
3250
2918
|
{
|
|
3251
2919
|
title: "Update Subscriber",
|
|
@@ -3284,8 +2952,8 @@ Call again with confirmed=true to proceed.`
|
|
|
3284
2952
|
"delete_subscriber",
|
|
3285
2953
|
"Remove a subscriber. Set confirmed=false to preview first.",
|
|
3286
2954
|
{
|
|
3287
|
-
subscriber_id:
|
|
3288
|
-
confirmed:
|
|
2955
|
+
subscriber_id: z12.string().describe("The subscriber ID to delete"),
|
|
2956
|
+
confirmed: z12.boolean().default(false).describe("Set to true to confirm deletion")
|
|
3289
2957
|
},
|
|
3290
2958
|
{
|
|
3291
2959
|
title: "Delete Subscriber",
|
|
@@ -3317,6 +2985,823 @@ Call again with confirmed=true to delete.`
|
|
|
3317
2985
|
);
|
|
3318
2986
|
}
|
|
3319
2987
|
|
|
2988
|
+
// src/tools/wordpress.ts
|
|
2989
|
+
import { z as z13 } from "zod";
|
|
2990
|
+
function registerWordPressTools(server2, client2) {
|
|
2991
|
+
server2.tool(
|
|
2992
|
+
"wp_create_post",
|
|
2993
|
+
"Create a new WordPress post. Defaults to draft status. Set confirmed=false to preview before publishing.",
|
|
2994
|
+
{
|
|
2995
|
+
title: z13.string().optional().describe("Post title"),
|
|
2996
|
+
content: z13.string().optional().describe("Post content (HTML)"),
|
|
2997
|
+
status: z13.enum(["draft", "publish", "pending", "private", "future"]).default("draft").describe("Post status. Use 'future' with date for scheduling."),
|
|
2998
|
+
categories: z13.array(z13.number()).optional().describe("Array of category IDs"),
|
|
2999
|
+
tags: z13.array(z13.number()).optional().describe("Array of tag IDs"),
|
|
3000
|
+
featured_media: z13.number().optional().describe("Featured image attachment ID"),
|
|
3001
|
+
excerpt: z13.string().optional().describe("Post excerpt"),
|
|
3002
|
+
date: z13.string().optional().describe(
|
|
3003
|
+
"Publication date in ISO 8601 format. Required when status is 'future'."
|
|
3004
|
+
),
|
|
3005
|
+
slug: z13.string().optional().describe("URL slug for the post"),
|
|
3006
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3007
|
+
"Set to true to confirm creation. If false, returns a preview for approval. Required when status is 'publish'."
|
|
3008
|
+
)
|
|
3009
|
+
},
|
|
3010
|
+
{
|
|
3011
|
+
title: "Create WordPress Post",
|
|
3012
|
+
readOnlyHint: false,
|
|
3013
|
+
destructiveHint: false,
|
|
3014
|
+
idempotentHint: false,
|
|
3015
|
+
openWorldHint: true
|
|
3016
|
+
},
|
|
3017
|
+
async (args) => {
|
|
3018
|
+
if (args.confirmed !== true && (args.status === "publish" || args.status === "future")) {
|
|
3019
|
+
const preview = `## WordPress Post Preview
|
|
3020
|
+
|
|
3021
|
+
**Title:** ${args.title ?? "(no title)"}
|
|
3022
|
+
**Status:** ${args.status}
|
|
3023
|
+
**Content length:** ${(args.content ?? "").length} characters
|
|
3024
|
+
` + (args.categories?.length ? `**Categories:** ${args.categories.join(", ")}
|
|
3025
|
+
` : "") + (args.tags?.length ? `**Tags:** ${args.tags.join(", ")}
|
|
3026
|
+
` : "") + (args.date ? `**Date:** ${args.date}
|
|
3027
|
+
` : "") + (args.slug ? `**Slug:** ${args.slug}
|
|
3028
|
+
` : "") + (args.excerpt ? `**Excerpt:** ${args.excerpt.slice(0, 200)}
|
|
3029
|
+
` : "") + `
|
|
3030
|
+
**Action:** This will ${args.status === "publish" ? "publish immediately" : "schedule"} on your WordPress site.
|
|
3031
|
+
|
|
3032
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3033
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3034
|
+
}
|
|
3035
|
+
const body = {
|
|
3036
|
+
title: args.title,
|
|
3037
|
+
content: args.content,
|
|
3038
|
+
status: args.status
|
|
3039
|
+
};
|
|
3040
|
+
if (args.categories) body.categories = args.categories;
|
|
3041
|
+
if (args.tags) body.tags = args.tags;
|
|
3042
|
+
if (args.featured_media) body.featured_media = args.featured_media;
|
|
3043
|
+
if (args.excerpt) body.excerpt = args.excerpt;
|
|
3044
|
+
if (args.date) body.date = args.date;
|
|
3045
|
+
if (args.slug) body.slug = args.slug;
|
|
3046
|
+
const result = await client2.wpCreatePost(body);
|
|
3047
|
+
return {
|
|
3048
|
+
content: [
|
|
3049
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3050
|
+
]
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
3054
|
+
server2.tool(
|
|
3055
|
+
"wp_update_post",
|
|
3056
|
+
"Update an existing WordPress post by ID.",
|
|
3057
|
+
{
|
|
3058
|
+
post_id: z13.number().describe("The WordPress post ID to update"),
|
|
3059
|
+
title: z13.string().optional().describe("New post title"),
|
|
3060
|
+
content: z13.string().optional().describe("New post content (HTML)"),
|
|
3061
|
+
status: z13.enum(["draft", "publish", "pending", "private", "future"]).optional().describe("New post status"),
|
|
3062
|
+
categories: z13.array(z13.number()).optional().describe("New category IDs"),
|
|
3063
|
+
tags: z13.array(z13.number()).optional().describe("New tag IDs"),
|
|
3064
|
+
featured_media: z13.number().optional().describe("New featured image attachment ID"),
|
|
3065
|
+
excerpt: z13.string().optional().describe("New post excerpt"),
|
|
3066
|
+
date: z13.string().optional().describe("New publication date (ISO 8601)"),
|
|
3067
|
+
slug: z13.string().optional().describe("New URL slug")
|
|
3068
|
+
},
|
|
3069
|
+
{
|
|
3070
|
+
title: "Update WordPress Post",
|
|
3071
|
+
readOnlyHint: false,
|
|
3072
|
+
destructiveHint: false,
|
|
3073
|
+
idempotentHint: true,
|
|
3074
|
+
openWorldHint: true
|
|
3075
|
+
},
|
|
3076
|
+
async (args) => {
|
|
3077
|
+
const { post_id, ...data } = args;
|
|
3078
|
+
const result = await client2.wpUpdatePost(post_id, data);
|
|
3079
|
+
return {
|
|
3080
|
+
content: [
|
|
3081
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3082
|
+
]
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
);
|
|
3086
|
+
server2.tool(
|
|
3087
|
+
"wp_publish_post",
|
|
3088
|
+
"Change a WordPress post's status to 'publish'. Requires confirmation.",
|
|
3089
|
+
{
|
|
3090
|
+
post_id: z13.number().describe("The WordPress post ID to publish"),
|
|
3091
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3092
|
+
"Set to true to confirm publishing. If false, returns a preview."
|
|
3093
|
+
)
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
title: "Publish WordPress Post",
|
|
3097
|
+
readOnlyHint: false,
|
|
3098
|
+
destructiveHint: false,
|
|
3099
|
+
idempotentHint: true,
|
|
3100
|
+
openWorldHint: true
|
|
3101
|
+
},
|
|
3102
|
+
async (args) => {
|
|
3103
|
+
if (args.confirmed !== true) {
|
|
3104
|
+
try {
|
|
3105
|
+
const post = await client2.wpGetPost(args.post_id);
|
|
3106
|
+
const preview = `## Publish WordPress Post
|
|
3107
|
+
|
|
3108
|
+
**Post ID:** ${args.post_id}
|
|
3109
|
+
**Title:** ${post.title?.rendered ?? "(no title)"}
|
|
3110
|
+
**Current Status:** ${post.status ?? "unknown"}
|
|
3111
|
+
|
|
3112
|
+
This will publish this post on your WordPress site, making it publicly visible.
|
|
3113
|
+
|
|
3114
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3115
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3116
|
+
} catch {
|
|
3117
|
+
const preview = `## Publish WordPress Post
|
|
3118
|
+
|
|
3119
|
+
**Post ID:** ${args.post_id}
|
|
3120
|
+
|
|
3121
|
+
This will publish this post. Call this tool again with confirmed=true to proceed.`;
|
|
3122
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
const result = await client2.wpUpdatePost(args.post_id, {
|
|
3126
|
+
status: "publish"
|
|
3127
|
+
});
|
|
3128
|
+
return {
|
|
3129
|
+
content: [
|
|
3130
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3131
|
+
]
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
);
|
|
3135
|
+
server2.tool(
|
|
3136
|
+
"wp_list_posts",
|
|
3137
|
+
"List WordPress posts with optional filters.",
|
|
3138
|
+
{
|
|
3139
|
+
status: z13.string().optional().describe(
|
|
3140
|
+
"Filter by status: draft, publish, pending, private, future, trash"
|
|
3141
|
+
),
|
|
3142
|
+
per_page: z13.string().optional().describe("Number of posts per page (default 10, max 100)"),
|
|
3143
|
+
page: z13.string().optional().describe("Page number for pagination"),
|
|
3144
|
+
search: z13.string().optional().describe("Search posts by keyword"),
|
|
3145
|
+
categories: z13.string().optional().describe("Filter by category ID (comma-separated)"),
|
|
3146
|
+
tags: z13.string().optional().describe("Filter by tag ID (comma-separated)")
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
title: "List WordPress Posts",
|
|
3150
|
+
readOnlyHint: true,
|
|
3151
|
+
destructiveHint: false,
|
|
3152
|
+
idempotentHint: true,
|
|
3153
|
+
openWorldHint: true
|
|
3154
|
+
},
|
|
3155
|
+
async (args) => {
|
|
3156
|
+
const params = {};
|
|
3157
|
+
if (args.status) params.status = args.status;
|
|
3158
|
+
if (args.per_page) params.per_page = args.per_page;
|
|
3159
|
+
if (args.page) params.page = args.page;
|
|
3160
|
+
if (args.search) params.search = args.search;
|
|
3161
|
+
if (args.categories) params.categories = args.categories;
|
|
3162
|
+
if (args.tags) params.tags = args.tags;
|
|
3163
|
+
const result = await client2.wpListPosts(params);
|
|
3164
|
+
return {
|
|
3165
|
+
content: [
|
|
3166
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3167
|
+
]
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
);
|
|
3171
|
+
server2.tool(
|
|
3172
|
+
"wp_get_post",
|
|
3173
|
+
"Get detailed information about a specific WordPress post by ID.",
|
|
3174
|
+
{
|
|
3175
|
+
post_id: z13.number().describe("The WordPress post ID to retrieve")
|
|
3176
|
+
},
|
|
3177
|
+
{
|
|
3178
|
+
title: "Get WordPress Post",
|
|
3179
|
+
readOnlyHint: true,
|
|
3180
|
+
destructiveHint: false,
|
|
3181
|
+
idempotentHint: true,
|
|
3182
|
+
openWorldHint: true
|
|
3183
|
+
},
|
|
3184
|
+
async (args) => {
|
|
3185
|
+
const result = await client2.wpGetPost(args.post_id);
|
|
3186
|
+
return {
|
|
3187
|
+
content: [
|
|
3188
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3189
|
+
]
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
);
|
|
3193
|
+
server2.tool(
|
|
3194
|
+
"wp_delete_post",
|
|
3195
|
+
"Trash a WordPress post. Use force=true to permanently delete. Requires confirmation.",
|
|
3196
|
+
{
|
|
3197
|
+
post_id: z13.number().describe("The WordPress post ID to delete"),
|
|
3198
|
+
force: z13.boolean().default(false).describe("Set to true to permanently delete instead of trashing"),
|
|
3199
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3200
|
+
"Set to true to confirm deletion. If false, returns a preview."
|
|
3201
|
+
)
|
|
3202
|
+
},
|
|
3203
|
+
{
|
|
3204
|
+
title: "Delete WordPress Post",
|
|
3205
|
+
readOnlyHint: false,
|
|
3206
|
+
destructiveHint: true,
|
|
3207
|
+
idempotentHint: true,
|
|
3208
|
+
openWorldHint: true
|
|
3209
|
+
},
|
|
3210
|
+
async (args) => {
|
|
3211
|
+
if (args.confirmed !== true) {
|
|
3212
|
+
const action = args.force ? "permanently delete" : "move to trash";
|
|
3213
|
+
const preview = `## Delete WordPress Post
|
|
3214
|
+
|
|
3215
|
+
**Post ID:** ${args.post_id}
|
|
3216
|
+
**Action:** ${action}
|
|
3217
|
+
|
|
3218
|
+
${args.force ? "WARNING: This will permanently delete the post and cannot be undone.\n\n" : "The post will be moved to trash and can be restored later.\n\n"}Call this tool again with confirmed=true to proceed.`;
|
|
3219
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3220
|
+
}
|
|
3221
|
+
const params = {};
|
|
3222
|
+
if (args.force) params.force = "true";
|
|
3223
|
+
const result = await client2.wpDeletePost(args.post_id, params);
|
|
3224
|
+
return {
|
|
3225
|
+
content: [
|
|
3226
|
+
{
|
|
3227
|
+
type: "text",
|
|
3228
|
+
text: args.force ? "Post permanently deleted." : JSON.stringify(result, null, 2)
|
|
3229
|
+
}
|
|
3230
|
+
]
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
);
|
|
3234
|
+
server2.tool(
|
|
3235
|
+
"wp_upload_media",
|
|
3236
|
+
"Upload an image from a URL to the WordPress media library.",
|
|
3237
|
+
{
|
|
3238
|
+
url: z13.string().url().describe("Public URL of the image to upload to WordPress"),
|
|
3239
|
+
filename: z13.string().optional().describe("Optional filename for the uploaded image")
|
|
3240
|
+
},
|
|
3241
|
+
{
|
|
3242
|
+
title: "Upload WordPress Media",
|
|
3243
|
+
readOnlyHint: false,
|
|
3244
|
+
destructiveHint: false,
|
|
3245
|
+
idempotentHint: false,
|
|
3246
|
+
openWorldHint: true
|
|
3247
|
+
},
|
|
3248
|
+
async (args) => {
|
|
3249
|
+
const result = await client2.wpUploadMedia({
|
|
3250
|
+
url: args.url,
|
|
3251
|
+
filename: args.filename
|
|
3252
|
+
});
|
|
3253
|
+
return {
|
|
3254
|
+
content: [
|
|
3255
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3256
|
+
]
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
);
|
|
3260
|
+
server2.tool(
|
|
3261
|
+
"wp_list_categories",
|
|
3262
|
+
"List all available WordPress categories.",
|
|
3263
|
+
{},
|
|
3264
|
+
{
|
|
3265
|
+
title: "List WordPress Categories",
|
|
3266
|
+
readOnlyHint: true,
|
|
3267
|
+
destructiveHint: false,
|
|
3268
|
+
idempotentHint: true,
|
|
3269
|
+
openWorldHint: true
|
|
3270
|
+
},
|
|
3271
|
+
async () => {
|
|
3272
|
+
const result = await client2.wpListCategories();
|
|
3273
|
+
return {
|
|
3274
|
+
content: [
|
|
3275
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3276
|
+
]
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
);
|
|
3280
|
+
server2.tool(
|
|
3281
|
+
"wp_list_tags",
|
|
3282
|
+
"List all available WordPress tags.",
|
|
3283
|
+
{},
|
|
3284
|
+
{
|
|
3285
|
+
title: "List WordPress Tags",
|
|
3286
|
+
readOnlyHint: true,
|
|
3287
|
+
destructiveHint: false,
|
|
3288
|
+
idempotentHint: true,
|
|
3289
|
+
openWorldHint: true
|
|
3290
|
+
},
|
|
3291
|
+
async () => {
|
|
3292
|
+
const result = await client2.wpListTags();
|
|
3293
|
+
return {
|
|
3294
|
+
content: [
|
|
3295
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3296
|
+
]
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
);
|
|
3300
|
+
server2.tool(
|
|
3301
|
+
"wp_create_page",
|
|
3302
|
+
"Create a new WordPress page. Defaults to draft status.",
|
|
3303
|
+
{
|
|
3304
|
+
title: z13.string().optional().describe("Page title"),
|
|
3305
|
+
content: z13.string().optional().describe("Page content (HTML)"),
|
|
3306
|
+
status: z13.enum(["draft", "publish", "pending", "private"]).default("draft").describe("Page status"),
|
|
3307
|
+
excerpt: z13.string().optional().describe("Page excerpt"),
|
|
3308
|
+
slug: z13.string().optional().describe("URL slug for the page"),
|
|
3309
|
+
featured_media: z13.number().optional().describe("Featured image attachment ID"),
|
|
3310
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3311
|
+
"Set to true to confirm creation. Required when status is 'publish'."
|
|
3312
|
+
)
|
|
3313
|
+
},
|
|
3314
|
+
{
|
|
3315
|
+
title: "Create WordPress Page",
|
|
3316
|
+
readOnlyHint: false,
|
|
3317
|
+
destructiveHint: false,
|
|
3318
|
+
idempotentHint: false,
|
|
3319
|
+
openWorldHint: true
|
|
3320
|
+
},
|
|
3321
|
+
async (args) => {
|
|
3322
|
+
if (args.confirmed !== true && args.status === "publish") {
|
|
3323
|
+
const preview = `## WordPress Page Preview
|
|
3324
|
+
|
|
3325
|
+
**Title:** ${args.title ?? "(no title)"}
|
|
3326
|
+
**Status:** ${args.status}
|
|
3327
|
+
**Content length:** ${(args.content ?? "").length} characters
|
|
3328
|
+
` + (args.slug ? `**Slug:** ${args.slug}
|
|
3329
|
+
` : "") + `
|
|
3330
|
+
This will publish the page immediately on your WordPress site.
|
|
3331
|
+
|
|
3332
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3333
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3334
|
+
}
|
|
3335
|
+
const body = {
|
|
3336
|
+
title: args.title,
|
|
3337
|
+
content: args.content,
|
|
3338
|
+
status: args.status
|
|
3339
|
+
};
|
|
3340
|
+
if (args.excerpt) body.excerpt = args.excerpt;
|
|
3341
|
+
if (args.slug) body.slug = args.slug;
|
|
3342
|
+
if (args.featured_media) body.featured_media = args.featured_media;
|
|
3343
|
+
const result = await client2.wpCreatePage(body);
|
|
3344
|
+
return {
|
|
3345
|
+
content: [
|
|
3346
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3347
|
+
]
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// src/tools/ghost.ts
|
|
3354
|
+
import { z as z14 } from "zod";
|
|
3355
|
+
function registerGhostTools(server2, client2) {
|
|
3356
|
+
server2.tool(
|
|
3357
|
+
"ghost_create_post",
|
|
3358
|
+
"Create a post on Ghost. Defaults to draft status. Set confirmed=false to preview before publishing.",
|
|
3359
|
+
{
|
|
3360
|
+
title: z14.string().describe("Post title"),
|
|
3361
|
+
html: z14.string().optional().describe("HTML content of the post"),
|
|
3362
|
+
status: z14.enum(["draft", "published", "scheduled"]).default("draft").describe("Post status: draft, published, or scheduled"),
|
|
3363
|
+
tags: z14.array(z14.string()).optional().describe("Tag names to apply to the post"),
|
|
3364
|
+
featured: z14.boolean().optional().describe("Whether the post is featured"),
|
|
3365
|
+
custom_excerpt: z14.string().optional().describe("Custom excerpt for the post"),
|
|
3366
|
+
feature_image: z14.string().optional().describe("URL of the feature image"),
|
|
3367
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3368
|
+
"Set to true to confirm creation. Required when status is 'published'. Drafts are created immediately."
|
|
3369
|
+
)
|
|
3370
|
+
},
|
|
3371
|
+
{
|
|
3372
|
+
title: "Create Ghost Post",
|
|
3373
|
+
readOnlyHint: false,
|
|
3374
|
+
destructiveHint: false,
|
|
3375
|
+
idempotentHint: false,
|
|
3376
|
+
openWorldHint: true
|
|
3377
|
+
},
|
|
3378
|
+
async (args) => {
|
|
3379
|
+
const needsConfirmation = args.status !== "draft";
|
|
3380
|
+
if (needsConfirmation && args.confirmed !== true) {
|
|
3381
|
+
const preview = `## Ghost Post Preview
|
|
3382
|
+
|
|
3383
|
+
**Title:** ${args.title}
|
|
3384
|
+
**Status:** ${args.status}
|
|
3385
|
+
` + (args.tags?.length ? `**Tags:** ${args.tags.join(", ")}
|
|
3386
|
+
` : "") + (args.featured ? `**Featured:** yes
|
|
3387
|
+
` : "") + (args.custom_excerpt ? `**Excerpt:** ${args.custom_excerpt}
|
|
3388
|
+
` : "") + (args.feature_image ? `**Feature Image:** ${args.feature_image}
|
|
3389
|
+
` : "") + (args.html ? `
|
|
3390
|
+
**Content preview:** ${args.html.replace(/<[^>]*>/g, "").slice(0, 200)}...
|
|
3391
|
+
` : "") + `
|
|
3392
|
+
**Action:** This will be **${args.status}** on Ghost immediately.
|
|
3393
|
+
|
|
3394
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3395
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3396
|
+
}
|
|
3397
|
+
const body = {
|
|
3398
|
+
title: args.title,
|
|
3399
|
+
status: args.status
|
|
3400
|
+
};
|
|
3401
|
+
if (args.html) body.html = args.html;
|
|
3402
|
+
if (args.tags) body.tags = args.tags.map((name) => ({ name }));
|
|
3403
|
+
if (args.featured !== void 0) body.featured = args.featured;
|
|
3404
|
+
if (args.custom_excerpt) body.custom_excerpt = args.custom_excerpt;
|
|
3405
|
+
if (args.feature_image) body.feature_image = args.feature_image;
|
|
3406
|
+
const result = await client2.ghostCreatePost(body);
|
|
3407
|
+
return {
|
|
3408
|
+
content: [
|
|
3409
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3410
|
+
]
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
);
|
|
3414
|
+
server2.tool(
|
|
3415
|
+
"ghost_update_post",
|
|
3416
|
+
"Update an existing Ghost post. Requires updated_at for collision detection.",
|
|
3417
|
+
{
|
|
3418
|
+
post_id: z14.string().describe("The Ghost post ID to update"),
|
|
3419
|
+
updated_at: z14.string().describe(
|
|
3420
|
+
"The updated_at value from the post (required for collision detection)"
|
|
3421
|
+
),
|
|
3422
|
+
title: z14.string().optional().describe("Updated post title"),
|
|
3423
|
+
html: z14.string().optional().describe("Updated HTML content"),
|
|
3424
|
+
status: z14.enum(["draft", "published", "scheduled"]).optional().describe("Updated status"),
|
|
3425
|
+
tags: z14.array(z14.string()).optional().describe("Updated tag names"),
|
|
3426
|
+
featured: z14.boolean().optional().describe("Whether the post is featured"),
|
|
3427
|
+
custom_excerpt: z14.string().optional().describe("Updated custom excerpt"),
|
|
3428
|
+
feature_image: z14.string().optional().describe("Updated feature image URL")
|
|
3429
|
+
},
|
|
3430
|
+
{
|
|
3431
|
+
title: "Update Ghost Post",
|
|
3432
|
+
readOnlyHint: false,
|
|
3433
|
+
destructiveHint: false,
|
|
3434
|
+
idempotentHint: true,
|
|
3435
|
+
openWorldHint: true
|
|
3436
|
+
},
|
|
3437
|
+
async (args) => {
|
|
3438
|
+
const data = {
|
|
3439
|
+
updated_at: args.updated_at
|
|
3440
|
+
};
|
|
3441
|
+
if (args.title) data.title = args.title;
|
|
3442
|
+
if (args.html) data.html = args.html;
|
|
3443
|
+
if (args.status) data.status = args.status;
|
|
3444
|
+
if (args.tags) data.tags = args.tags.map((name) => ({ name }));
|
|
3445
|
+
if (args.featured !== void 0) data.featured = args.featured;
|
|
3446
|
+
if (args.custom_excerpt) data.custom_excerpt = args.custom_excerpt;
|
|
3447
|
+
if (args.feature_image) data.feature_image = args.feature_image;
|
|
3448
|
+
const result = await client2.ghostUpdatePost(args.post_id, data);
|
|
3449
|
+
return {
|
|
3450
|
+
content: [
|
|
3451
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3452
|
+
]
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
);
|
|
3456
|
+
server2.tool(
|
|
3457
|
+
"ghost_publish_post",
|
|
3458
|
+
"Publish a Ghost draft post. This makes the post live. Set confirmed=false to preview first.",
|
|
3459
|
+
{
|
|
3460
|
+
post_id: z14.string().describe("The Ghost post ID to publish"),
|
|
3461
|
+
updated_at: z14.string().describe(
|
|
3462
|
+
"The updated_at value from the post (required for collision detection)"
|
|
3463
|
+
),
|
|
3464
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3465
|
+
"Set to true to confirm publishing. If false or missing, returns a confirmation prompt."
|
|
3466
|
+
)
|
|
3467
|
+
},
|
|
3468
|
+
{
|
|
3469
|
+
title: "Publish Ghost Post",
|
|
3470
|
+
readOnlyHint: false,
|
|
3471
|
+
destructiveHint: false,
|
|
3472
|
+
idempotentHint: false,
|
|
3473
|
+
openWorldHint: true
|
|
3474
|
+
},
|
|
3475
|
+
async (args) => {
|
|
3476
|
+
if (args.confirmed !== true) {
|
|
3477
|
+
let postInfo = "";
|
|
3478
|
+
try {
|
|
3479
|
+
const post = await client2.ghostGetPost(args.post_id);
|
|
3480
|
+
postInfo = `**Title:** ${post?.title ?? "Unknown"}
|
|
3481
|
+
**Current Status:** ${post?.status ?? "Unknown"}
|
|
3482
|
+
` + (post?.tags?.length ? `**Tags:** ${post.tags.map((t) => t.name).join(", ")}
|
|
3483
|
+
` : "");
|
|
3484
|
+
} catch {
|
|
3485
|
+
postInfo = `**Post ID:** ${args.post_id}
|
|
3486
|
+
`;
|
|
3487
|
+
}
|
|
3488
|
+
const preview = `## Publish Ghost Post Confirmation
|
|
3489
|
+
|
|
3490
|
+
` + postInfo + `
|
|
3491
|
+
**Action:** This will make the post **live** on your Ghost site.
|
|
3492
|
+
|
|
3493
|
+
Call this tool again with confirmed=true to publish.`;
|
|
3494
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3495
|
+
}
|
|
3496
|
+
const result = await client2.ghostPublishPost(args.post_id, {
|
|
3497
|
+
updated_at: args.updated_at
|
|
3498
|
+
});
|
|
3499
|
+
return {
|
|
3500
|
+
content: [
|
|
3501
|
+
{ type: "text", text: `Post published successfully.
|
|
3502
|
+
|
|
3503
|
+
${JSON.stringify(result, null, 2)}` }
|
|
3504
|
+
]
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
);
|
|
3508
|
+
server2.tool(
|
|
3509
|
+
"ghost_list_posts",
|
|
3510
|
+
"List posts from Ghost with optional status filter.",
|
|
3511
|
+
{
|
|
3512
|
+
status: z14.enum(["draft", "published", "scheduled"]).optional().describe("Filter posts by status"),
|
|
3513
|
+
limit: z14.string().optional().describe("Number of posts to return (default: 15)"),
|
|
3514
|
+
page: z14.string().optional().describe("Page number for pagination")
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
title: "List Ghost Posts",
|
|
3518
|
+
readOnlyHint: true,
|
|
3519
|
+
destructiveHint: false,
|
|
3520
|
+
idempotentHint: true,
|
|
3521
|
+
openWorldHint: true
|
|
3522
|
+
},
|
|
3523
|
+
async (args) => {
|
|
3524
|
+
const params = {};
|
|
3525
|
+
if (args.status) params.status = args.status;
|
|
3526
|
+
if (args.limit) params.limit = args.limit;
|
|
3527
|
+
if (args.page) params.page = args.page;
|
|
3528
|
+
const result = await client2.ghostListPosts(params);
|
|
3529
|
+
const posts = result?.posts ?? [];
|
|
3530
|
+
if (posts.length === 0) {
|
|
3531
|
+
return {
|
|
3532
|
+
content: [
|
|
3533
|
+
{
|
|
3534
|
+
type: "text",
|
|
3535
|
+
text: "No Ghost posts found matching your filters."
|
|
3536
|
+
}
|
|
3537
|
+
]
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
let text = `## Ghost Posts (${posts.length}`;
|
|
3541
|
+
if (result?.meta?.pagination?.total != null) {
|
|
3542
|
+
text += ` of ${result.meta.pagination.total}`;
|
|
3543
|
+
}
|
|
3544
|
+
text += ")\n\n";
|
|
3545
|
+
for (const p of posts) {
|
|
3546
|
+
const status = (p.status ?? "unknown").toUpperCase();
|
|
3547
|
+
const date = p.published_at ? new Date(p.published_at).toLocaleString() : p.created_at ? new Date(p.created_at).toLocaleString() : "";
|
|
3548
|
+
const tags = p.tags?.length > 0 ? ` | Tags: ${p.tags.map((t) => t.name).join(", ")}` : "";
|
|
3549
|
+
text += `- **[${status}]** "${p.title ?? "(no title)"}"
|
|
3550
|
+
`;
|
|
3551
|
+
text += ` ID: \`${p.id}\``;
|
|
3552
|
+
if (date) text += ` | ${p.published_at ? "Published" : "Created"}: ${date}`;
|
|
3553
|
+
text += tags;
|
|
3554
|
+
text += "\n";
|
|
3555
|
+
}
|
|
3556
|
+
return { content: [{ type: "text", text }] };
|
|
3557
|
+
}
|
|
3558
|
+
);
|
|
3559
|
+
server2.tool(
|
|
3560
|
+
"ghost_get_post",
|
|
3561
|
+
"Get detailed information about a specific Ghost post by ID.",
|
|
3562
|
+
{
|
|
3563
|
+
post_id: z14.string().describe("The Ghost post ID to retrieve")
|
|
3564
|
+
},
|
|
3565
|
+
{
|
|
3566
|
+
title: "Get Ghost Post",
|
|
3567
|
+
readOnlyHint: true,
|
|
3568
|
+
destructiveHint: false,
|
|
3569
|
+
idempotentHint: true,
|
|
3570
|
+
openWorldHint: true
|
|
3571
|
+
},
|
|
3572
|
+
async (args) => {
|
|
3573
|
+
const result = await client2.ghostGetPost(args.post_id);
|
|
3574
|
+
let text = `## Ghost Post Details
|
|
3575
|
+
|
|
3576
|
+
`;
|
|
3577
|
+
text += `**Title:** ${result?.title ?? "Unknown"}
|
|
3578
|
+
`;
|
|
3579
|
+
text += `**ID:** \`${result?.id}\`
|
|
3580
|
+
`;
|
|
3581
|
+
text += `**Status:** ${result?.status ?? "Unknown"}
|
|
3582
|
+
`;
|
|
3583
|
+
text += `**Slug:** ${result?.slug ?? "N/A"}
|
|
3584
|
+
`;
|
|
3585
|
+
if (result?.url) text += `**URL:** ${result.url}
|
|
3586
|
+
`;
|
|
3587
|
+
if (result?.custom_excerpt) text += `**Excerpt:** ${result.custom_excerpt}
|
|
3588
|
+
`;
|
|
3589
|
+
if (result?.feature_image) text += `**Feature Image:** ${result.feature_image}
|
|
3590
|
+
`;
|
|
3591
|
+
if (result?.featured) text += `**Featured:** yes
|
|
3592
|
+
`;
|
|
3593
|
+
if (result?.tags?.length > 0) {
|
|
3594
|
+
text += `**Tags:** ${result.tags.map((t) => t.name).join(", ")}
|
|
3595
|
+
`;
|
|
3596
|
+
}
|
|
3597
|
+
if (result?.authors?.length > 0) {
|
|
3598
|
+
text += `**Authors:** ${result.authors.map((a) => a.name).join(", ")}
|
|
3599
|
+
`;
|
|
3600
|
+
}
|
|
3601
|
+
if (result?.published_at) {
|
|
3602
|
+
text += `**Published:** ${new Date(result.published_at).toLocaleString()}
|
|
3603
|
+
`;
|
|
3604
|
+
}
|
|
3605
|
+
text += `**Created:** ${result?.created_at ? new Date(result.created_at).toLocaleString() : "N/A"}
|
|
3606
|
+
`;
|
|
3607
|
+
text += `**Updated:** ${result?.updated_at ?? "N/A"}
|
|
3608
|
+
`;
|
|
3609
|
+
text += `
|
|
3610
|
+
*Use updated_at value \`${result?.updated_at}\` when updating or publishing this post.*
|
|
3611
|
+
`;
|
|
3612
|
+
if (result?.html) {
|
|
3613
|
+
const plainText = result.html.replace(/<[^>]*>/g, "");
|
|
3614
|
+
const preview = plainText.length > 500 ? plainText.slice(0, 500) + "..." : plainText;
|
|
3615
|
+
text += `
|
|
3616
|
+
### Content Preview
|
|
3617
|
+
${preview}
|
|
3618
|
+
`;
|
|
3619
|
+
}
|
|
3620
|
+
return { content: [{ type: "text", text }] };
|
|
3621
|
+
}
|
|
3622
|
+
);
|
|
3623
|
+
server2.tool(
|
|
3624
|
+
"ghost_send_newsletter",
|
|
3625
|
+
"Send a Ghost post as a newsletter to subscribers. The post must exist as a draft. This action cannot be undone. Set confirmed=false to preview first.",
|
|
3626
|
+
{
|
|
3627
|
+
post_id: z14.string().describe("The Ghost post ID to send as newsletter"),
|
|
3628
|
+
updated_at: z14.string().describe("The updated_at value from the post"),
|
|
3629
|
+
newsletter_id: z14.string().describe(
|
|
3630
|
+
"The Ghost newsletter ID to send through. Use ghost_list_newsletters to find available newsletters."
|
|
3631
|
+
),
|
|
3632
|
+
email_only: z14.boolean().default(false).describe(
|
|
3633
|
+
"If true, sends email only without publishing the post on the site"
|
|
3634
|
+
),
|
|
3635
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3636
|
+
"Set to true to confirm and send. If false or missing, returns a confirmation prompt."
|
|
3637
|
+
)
|
|
3638
|
+
},
|
|
3639
|
+
{
|
|
3640
|
+
title: "Send Ghost Newsletter",
|
|
3641
|
+
readOnlyHint: false,
|
|
3642
|
+
destructiveHint: false,
|
|
3643
|
+
idempotentHint: false,
|
|
3644
|
+
openWorldHint: true
|
|
3645
|
+
},
|
|
3646
|
+
async (args) => {
|
|
3647
|
+
if (args.confirmed !== true) {
|
|
3648
|
+
let postInfo = "";
|
|
3649
|
+
let memberCount = "unknown";
|
|
3650
|
+
let newsletterName = "unknown";
|
|
3651
|
+
try {
|
|
3652
|
+
const post = await client2.ghostGetPost(args.post_id);
|
|
3653
|
+
postInfo = `**Title:** ${post?.title ?? "Unknown"}
|
|
3654
|
+
**Current Status:** ${post?.status ?? "Unknown"}
|
|
3655
|
+
`;
|
|
3656
|
+
} catch {
|
|
3657
|
+
postInfo = `**Post ID:** ${args.post_id}
|
|
3658
|
+
`;
|
|
3659
|
+
}
|
|
3660
|
+
try {
|
|
3661
|
+
const members = await client2.ghostListMembers({ limit: "1" });
|
|
3662
|
+
memberCount = String(
|
|
3663
|
+
members?.meta?.pagination?.total ?? "unknown"
|
|
3664
|
+
);
|
|
3665
|
+
} catch {
|
|
3666
|
+
}
|
|
3667
|
+
try {
|
|
3668
|
+
const newsletters = await client2.ghostListNewsletters();
|
|
3669
|
+
const nl = (newsletters?.newsletters ?? []).find(
|
|
3670
|
+
(n) => n.id === args.newsletter_id
|
|
3671
|
+
);
|
|
3672
|
+
if (nl) newsletterName = nl.name;
|
|
3673
|
+
} catch {
|
|
3674
|
+
}
|
|
3675
|
+
const preview = `## Send Ghost Newsletter Confirmation
|
|
3676
|
+
|
|
3677
|
+
` + postInfo + `**Newsletter:** ${newsletterName} (\`${args.newsletter_id}\`)
|
|
3678
|
+
**Members:** ~${memberCount} subscribers
|
|
3679
|
+
**Email Only:** ${args.email_only ? "yes (post will NOT be published on site)" : "no (post will also be published)"}
|
|
3680
|
+
|
|
3681
|
+
**This action cannot be undone.** Once sent, the email goes to all newsletter subscribers.
|
|
3682
|
+
|
|
3683
|
+
Call this tool again with confirmed=true to send.`;
|
|
3684
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3685
|
+
}
|
|
3686
|
+
const result = await client2.ghostSendNewsletter(args.post_id, {
|
|
3687
|
+
updated_at: args.updated_at,
|
|
3688
|
+
newsletter_id: args.newsletter_id,
|
|
3689
|
+
email_only: args.email_only
|
|
3690
|
+
});
|
|
3691
|
+
return {
|
|
3692
|
+
content: [
|
|
3693
|
+
{
|
|
3694
|
+
type: "text",
|
|
3695
|
+
text: `Newsletter sent successfully.
|
|
3696
|
+
|
|
3697
|
+
${JSON.stringify(result, null, 2)}`
|
|
3698
|
+
}
|
|
3699
|
+
]
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
);
|
|
3703
|
+
server2.tool(
|
|
3704
|
+
"ghost_list_members",
|
|
3705
|
+
"List Ghost newsletter members (subscribers).",
|
|
3706
|
+
{
|
|
3707
|
+
limit: z14.string().optional().describe("Number of members to return (default: 15)"),
|
|
3708
|
+
page: z14.string().optional().describe("Page number for pagination"),
|
|
3709
|
+
filter: z14.string().optional().describe(
|
|
3710
|
+
"Ghost NQL filter string, e.g. 'status:free' or 'subscribed:true'"
|
|
3711
|
+
)
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
title: "List Ghost Members",
|
|
3715
|
+
readOnlyHint: true,
|
|
3716
|
+
destructiveHint: false,
|
|
3717
|
+
idempotentHint: true,
|
|
3718
|
+
openWorldHint: true
|
|
3719
|
+
},
|
|
3720
|
+
async (args) => {
|
|
3721
|
+
const params = {};
|
|
3722
|
+
if (args.limit) params.limit = args.limit;
|
|
3723
|
+
if (args.page) params.page = args.page;
|
|
3724
|
+
if (args.filter) params.filter = args.filter;
|
|
3725
|
+
const result = await client2.ghostListMembers(params);
|
|
3726
|
+
const members = result?.members ?? [];
|
|
3727
|
+
if (members.length === 0) {
|
|
3728
|
+
return {
|
|
3729
|
+
content: [
|
|
3730
|
+
{
|
|
3731
|
+
type: "text",
|
|
3732
|
+
text: "No Ghost members found matching your filters."
|
|
3733
|
+
}
|
|
3734
|
+
]
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
let text = `## Ghost Members (${members.length}`;
|
|
3738
|
+
if (result?.meta?.pagination?.total != null) {
|
|
3739
|
+
text += ` of ${result.meta.pagination.total}`;
|
|
3740
|
+
}
|
|
3741
|
+
text += ")\n\n";
|
|
3742
|
+
for (const m of members) {
|
|
3743
|
+
const status = m.status ?? "unknown";
|
|
3744
|
+
text += `- **${m.name || m.email}**`;
|
|
3745
|
+
if (m.name && m.email) text += ` (${m.email})`;
|
|
3746
|
+
text += ` | Status: ${status}`;
|
|
3747
|
+
if (m.created_at) {
|
|
3748
|
+
text += ` | Joined: ${new Date(m.created_at).toLocaleDateString()}`;
|
|
3749
|
+
}
|
|
3750
|
+
text += "\n";
|
|
3751
|
+
}
|
|
3752
|
+
return { content: [{ type: "text", text }] };
|
|
3753
|
+
}
|
|
3754
|
+
);
|
|
3755
|
+
server2.tool(
|
|
3756
|
+
"ghost_list_newsletters",
|
|
3757
|
+
"List configured newsletters in Ghost.",
|
|
3758
|
+
{},
|
|
3759
|
+
{
|
|
3760
|
+
title: "List Ghost Newsletters",
|
|
3761
|
+
readOnlyHint: true,
|
|
3762
|
+
destructiveHint: false,
|
|
3763
|
+
idempotentHint: true,
|
|
3764
|
+
openWorldHint: true
|
|
3765
|
+
},
|
|
3766
|
+
async () => {
|
|
3767
|
+
const result = await client2.ghostListNewsletters();
|
|
3768
|
+
const newsletters = result?.newsletters ?? [];
|
|
3769
|
+
if (newsletters.length === 0) {
|
|
3770
|
+
return {
|
|
3771
|
+
content: [
|
|
3772
|
+
{
|
|
3773
|
+
type: "text",
|
|
3774
|
+
text: "No newsletters configured in Ghost."
|
|
3775
|
+
}
|
|
3776
|
+
]
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
let text = `## Ghost Newsletters (${newsletters.length})
|
|
3780
|
+
|
|
3781
|
+
`;
|
|
3782
|
+
for (const nl of newsletters) {
|
|
3783
|
+
text += `- **${nl.name}**
|
|
3784
|
+
`;
|
|
3785
|
+
text += ` ID: \`${nl.id}\``;
|
|
3786
|
+
if (nl.description) text += ` | ${nl.description}`;
|
|
3787
|
+
text += `
|
|
3788
|
+
Status: ${nl.status ?? "active"}`;
|
|
3789
|
+
if (nl.subscribe_on_signup !== void 0) {
|
|
3790
|
+
text += ` | Subscribe on signup: ${nl.subscribe_on_signup ? "yes" : "no"}`;
|
|
3791
|
+
}
|
|
3792
|
+
if (nl.member_count != null) {
|
|
3793
|
+
text += ` | Members: ${nl.member_count}`;
|
|
3794
|
+
}
|
|
3795
|
+
text += "\n";
|
|
3796
|
+
}
|
|
3797
|
+
text += `
|
|
3798
|
+
*Use the newsletter ID when sending a post as a newsletter with ghost_send_newsletter.*
|
|
3799
|
+
`;
|
|
3800
|
+
return { content: [{ type: "text", text }] };
|
|
3801
|
+
}
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3320
3805
|
// src/index.ts
|
|
3321
3806
|
var apiKey = process.env.BUZZPOSTER_API_KEY;
|
|
3322
3807
|
var apiUrl = process.env.BUZZPOSTER_API_URL ?? "https://api.buzzposter.com";
|
|
@@ -3325,7 +3810,7 @@ if (!apiKey) {
|
|
|
3325
3810
|
"Error: BUZZPOSTER_API_KEY environment variable is required."
|
|
3326
3811
|
);
|
|
3327
3812
|
console.error(
|
|
3328
|
-
"Set it in your
|
|
3813
|
+
"Set it in your MCP config or export it in your shell."
|
|
3329
3814
|
);
|
|
3330
3815
|
process.exit(1);
|
|
3331
3816
|
}
|
|
@@ -3333,11 +3818,21 @@ var client = new BuzzPosterClient({ baseUrl: apiUrl, apiKey });
|
|
|
3333
3818
|
var server = new McpServer(
|
|
3334
3819
|
{
|
|
3335
3820
|
name: "buzzposter",
|
|
3336
|
-
version: "0.1.0"
|
|
3821
|
+
version: "0.1.0",
|
|
3822
|
+
icons: [
|
|
3823
|
+
{
|
|
3824
|
+
src: "https://api.buzzposter.com/favicon.svg",
|
|
3825
|
+
mimeType: "image/svg+xml",
|
|
3826
|
+
sizes: ["any"]
|
|
3827
|
+
},
|
|
3828
|
+
{
|
|
3829
|
+
src: "https://api.buzzposter.com/favicon.ico",
|
|
3830
|
+
mimeType: "image/x-icon",
|
|
3831
|
+
sizes: ["48x48"]
|
|
3832
|
+
}
|
|
3833
|
+
]
|
|
3337
3834
|
},
|
|
3338
|
-
{
|
|
3339
|
-
instructions: `When a tool response contains \`NEWSLETTER_HTML_PREVIEW:\` followed by HTML content, immediately render that HTML as an artifact so the user sees a live visual preview of the newsletter. Do not ask the user if they want to see it -- just render it automatically.`
|
|
3340
|
-
}
|
|
3835
|
+
{}
|
|
3341
3836
|
);
|
|
3342
3837
|
var allowDirectSend = false;
|
|
3343
3838
|
try {
|
|
@@ -3354,13 +3849,13 @@ registerNewsletterTools(server, client, { allowDirectSend });
|
|
|
3354
3849
|
registerRssTools(server, client);
|
|
3355
3850
|
registerAccountInfoTool(server, client);
|
|
3356
3851
|
registerBrandVoiceTools(server, client);
|
|
3357
|
-
registerKnowledgeTools(server, client);
|
|
3358
3852
|
registerAudienceTools(server, client);
|
|
3359
3853
|
registerNewsletterTemplateTools(server, client);
|
|
3360
3854
|
registerCalendarTools(server, client);
|
|
3361
3855
|
registerNotificationTools(server, client);
|
|
3362
|
-
registerPublishingRulesTools(server, client);
|
|
3363
3856
|
registerSourceTools(server, client);
|
|
3364
3857
|
registerNewsletterAdvancedTools(server, client);
|
|
3858
|
+
registerWordPressTools(server, client);
|
|
3859
|
+
registerGhostTools(server, client);
|
|
3365
3860
|
var transport = new StdioServerTransport();
|
|
3366
3861
|
await server.connect(transport);
|