@buzzposter/mcp 0.1.16 → 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/tools.js
CHANGED
|
@@ -6,8 +6,8 @@ var BuzzPosterClient = class {
|
|
|
6
6
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
7
7
|
this.apiKey = config.apiKey;
|
|
8
8
|
}
|
|
9
|
-
async request(method,
|
|
10
|
-
const url = new URL(`${this.baseUrl}${
|
|
9
|
+
async request(method, path2, body, query) {
|
|
10
|
+
const url = new URL(`${this.baseUrl}${path2}`);
|
|
11
11
|
if (query) {
|
|
12
12
|
for (const [k, v] of Object.entries(query)) {
|
|
13
13
|
if (v !== void 0 && v !== "") url.searchParams.set(k, v);
|
|
@@ -164,15 +164,8 @@ var BuzzPosterClient = class {
|
|
|
164
164
|
async listPostTemplates() {
|
|
165
165
|
return this.request("GET", "/api/v1/newsletters/post-templates");
|
|
166
166
|
}
|
|
167
|
-
async
|
|
168
|
-
return this.request("GET", "/api/v1/newsletters/
|
|
169
|
-
}
|
|
170
|
-
// Knowledge
|
|
171
|
-
async listKnowledge(params) {
|
|
172
|
-
return this.request("GET", "/api/v1/knowledge", void 0, params);
|
|
173
|
-
}
|
|
174
|
-
async createKnowledge(data) {
|
|
175
|
-
return this.request("POST", "/api/v1/knowledge", data);
|
|
167
|
+
async listEmailTemplates() {
|
|
168
|
+
return this.request("GET", "/api/v1/newsletters/email-templates");
|
|
176
169
|
}
|
|
177
170
|
// Brand Voice
|
|
178
171
|
async getBrandVoice() {
|
|
@@ -182,9 +175,15 @@ var BuzzPosterClient = class {
|
|
|
182
175
|
return this.request("PUT", "/api/v1/brand-voice", data);
|
|
183
176
|
}
|
|
184
177
|
// Audiences
|
|
178
|
+
async listAudiences() {
|
|
179
|
+
return this.request("GET", "/api/v1/audiences");
|
|
180
|
+
}
|
|
185
181
|
async getAudience(id) {
|
|
186
182
|
return this.request("GET", `/api/v1/audiences/${id}`);
|
|
187
183
|
}
|
|
184
|
+
async getAudienceByName(name) {
|
|
185
|
+
return this.request("GET", `/api/v1/audiences/by-name/${encodeURIComponent(name)}`);
|
|
186
|
+
}
|
|
188
187
|
async getDefaultAudience() {
|
|
189
188
|
return this.request("GET", "/api/v1/audiences/default");
|
|
190
189
|
}
|
|
@@ -194,6 +193,9 @@ var BuzzPosterClient = class {
|
|
|
194
193
|
async updateAudience(id, data) {
|
|
195
194
|
return this.request("PUT", `/api/v1/audiences/${id}`, data);
|
|
196
195
|
}
|
|
196
|
+
async deleteAudience(id) {
|
|
197
|
+
return this.request("DELETE", `/api/v1/audiences/${id}`);
|
|
198
|
+
}
|
|
197
199
|
// Newsletter Templates
|
|
198
200
|
async getTemplate(id) {
|
|
199
201
|
if (id) return this.request("GET", `/api/v1/templates/${id}`);
|
|
@@ -214,16 +216,6 @@ var BuzzPosterClient = class {
|
|
|
214
216
|
async duplicateTemplate(id) {
|
|
215
217
|
return this.request("POST", "/api/v1/templates/" + id + "/duplicate");
|
|
216
218
|
}
|
|
217
|
-
// Newsletter Archive
|
|
218
|
-
async listNewsletterArchive(params) {
|
|
219
|
-
return this.request("GET", "/api/v1/newsletter-archive", void 0, params);
|
|
220
|
-
}
|
|
221
|
-
async getArchivedNewsletter(id) {
|
|
222
|
-
return this.request("GET", `/api/v1/newsletter-archive/${id}`);
|
|
223
|
-
}
|
|
224
|
-
async saveNewsletterToArchive(data) {
|
|
225
|
-
return this.request("POST", "/api/v1/newsletter-archive", data);
|
|
226
|
-
}
|
|
227
219
|
// Calendar
|
|
228
220
|
async getCalendar(params) {
|
|
229
221
|
return this.request("GET", "/api/v1/calendar", void 0, params);
|
|
@@ -285,6 +277,37 @@ var BuzzPosterClient = class {
|
|
|
285
277
|
async getPublishingRules() {
|
|
286
278
|
return this.request("GET", "/api/v1/publishing-rules");
|
|
287
279
|
}
|
|
280
|
+
// Ghost
|
|
281
|
+
async ghostSetup(data) {
|
|
282
|
+
return this.request("POST", "/api/v1/ghost/setup", data);
|
|
283
|
+
}
|
|
284
|
+
async ghostCreatePost(data) {
|
|
285
|
+
return this.request("POST", "/api/v1/ghost/posts", data);
|
|
286
|
+
}
|
|
287
|
+
async ghostUpdatePost(id, data) {
|
|
288
|
+
return this.request("PUT", `/api/v1/ghost/posts/${id}`, data);
|
|
289
|
+
}
|
|
290
|
+
async ghostGetPost(id) {
|
|
291
|
+
return this.request("GET", `/api/v1/ghost/posts/${id}`);
|
|
292
|
+
}
|
|
293
|
+
async ghostListPosts(params) {
|
|
294
|
+
return this.request("GET", "/api/v1/ghost/posts", void 0, params);
|
|
295
|
+
}
|
|
296
|
+
async ghostDeletePost(id) {
|
|
297
|
+
return this.request("DELETE", `/api/v1/ghost/posts/${id}`);
|
|
298
|
+
}
|
|
299
|
+
async ghostPublishPost(id, data) {
|
|
300
|
+
return this.request("POST", `/api/v1/ghost/posts/${id}/publish`, data);
|
|
301
|
+
}
|
|
302
|
+
async ghostSendNewsletter(id, data) {
|
|
303
|
+
return this.request("POST", `/api/v1/ghost/posts/${id}/newsletter`, data);
|
|
304
|
+
}
|
|
305
|
+
async ghostListMembers(params) {
|
|
306
|
+
return this.request("GET", "/api/v1/ghost/members", void 0, params);
|
|
307
|
+
}
|
|
308
|
+
async ghostListNewsletters() {
|
|
309
|
+
return this.request("GET", "/api/v1/ghost/newsletters");
|
|
310
|
+
}
|
|
288
311
|
// Newsletter Validation
|
|
289
312
|
async validateNewsletter(data) {
|
|
290
313
|
return this.request("POST", "/api/v1/newsletters/validate", data);
|
|
@@ -297,6 +320,34 @@ var BuzzPosterClient = class {
|
|
|
297
320
|
async testBroadcast(id, data) {
|
|
298
321
|
return this.request("POST", `/api/v1/newsletters/broadcasts/${id}/test`, data);
|
|
299
322
|
}
|
|
323
|
+
// WordPress
|
|
324
|
+
async wpCreatePost(data) {
|
|
325
|
+
return this.request("POST", "/api/v1/wordpress/posts", data);
|
|
326
|
+
}
|
|
327
|
+
async wpUpdatePost(id, data) {
|
|
328
|
+
return this.request("PUT", `/api/v1/wordpress/posts/${id}`, data);
|
|
329
|
+
}
|
|
330
|
+
async wpGetPost(id) {
|
|
331
|
+
return this.request("GET", `/api/v1/wordpress/posts/${id}`);
|
|
332
|
+
}
|
|
333
|
+
async wpListPosts(params) {
|
|
334
|
+
return this.request("GET", "/api/v1/wordpress/posts", void 0, params);
|
|
335
|
+
}
|
|
336
|
+
async wpDeletePost(id, params) {
|
|
337
|
+
return this.request("DELETE", `/api/v1/wordpress/posts/${id}`, void 0, params);
|
|
338
|
+
}
|
|
339
|
+
async wpUploadMedia(data) {
|
|
340
|
+
return this.request("POST", "/api/v1/wordpress/media", data);
|
|
341
|
+
}
|
|
342
|
+
async wpListCategories() {
|
|
343
|
+
return this.request("GET", "/api/v1/wordpress/categories");
|
|
344
|
+
}
|
|
345
|
+
async wpListTags() {
|
|
346
|
+
return this.request("GET", "/api/v1/wordpress/tags");
|
|
347
|
+
}
|
|
348
|
+
async wpCreatePage(data) {
|
|
349
|
+
return this.request("POST", "/api/v1/wordpress/pages", data);
|
|
350
|
+
}
|
|
300
351
|
};
|
|
301
352
|
|
|
302
353
|
// src/tools/posts.ts
|
|
@@ -304,7 +355,7 @@ import { z } from "zod";
|
|
|
304
355
|
function registerPostTools(server, client) {
|
|
305
356
|
server.tool(
|
|
306
357
|
"post",
|
|
307
|
-
"Publish a post to one or more social platforms.
|
|
358
|
+
"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.",
|
|
308
359
|
{
|
|
309
360
|
content: z.string().optional().describe("The text content of the post"),
|
|
310
361
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -398,7 +449,7 @@ Call this tool again with confirmed=true to proceed.`;
|
|
|
398
449
|
);
|
|
399
450
|
server.tool(
|
|
400
451
|
"schedule_post",
|
|
401
|
-
"Schedule a post for future publication.
|
|
452
|
+
"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.",
|
|
402
453
|
{
|
|
403
454
|
content: z.string().optional().describe("The text content of the post"),
|
|
404
455
|
platforms: z.array(z.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
@@ -491,8 +542,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
491
542
|
const body = {
|
|
492
543
|
content: args.content,
|
|
493
544
|
platforms: args.platforms.map((p) => ({ platform: p })),
|
|
494
|
-
|
|
495
|
-
status: "draft"
|
|
545
|
+
isDraft: true
|
|
496
546
|
};
|
|
497
547
|
if (args.media_urls?.length) {
|
|
498
548
|
body.mediaItems = args.media_urls.map((url) => ({
|
|
@@ -505,7 +555,7 @@ Call this tool again with confirmed=true to schedule.`;
|
|
|
505
555
|
return {
|
|
506
556
|
content: [{
|
|
507
557
|
type: "text",
|
|
508
|
-
text: `ERROR: Post was saved with status "${result.status}" instead of "draft". Post ID: ${result.id ?? "unknown"}. Check
|
|
558
|
+
text: `ERROR: Post was saved with status "${result.status}" instead of "draft". Post ID: ${result.id ?? "unknown"}. Check your BuzzPoster dashboard immediately.`
|
|
509
559
|
}],
|
|
510
560
|
isError: true
|
|
511
561
|
};
|
|
@@ -835,7 +885,89 @@ This will post a public reply to the review. Call this tool again with confirmed
|
|
|
835
885
|
|
|
836
886
|
// src/tools/media.ts
|
|
837
887
|
import { z as z4 } from "zod";
|
|
888
|
+
var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
889
|
+
"image/png",
|
|
890
|
+
"image/jpeg",
|
|
891
|
+
"image/gif",
|
|
892
|
+
"image/webp",
|
|
893
|
+
"image/svg+xml"
|
|
894
|
+
]);
|
|
895
|
+
var MAX_THUMBNAIL_BYTES = 15e4;
|
|
896
|
+
async function fetchImageAsBase64(url) {
|
|
897
|
+
try {
|
|
898
|
+
const res = await fetch(url);
|
|
899
|
+
if (!res.ok) return null;
|
|
900
|
+
const contentType = res.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
901
|
+
if (!IMAGE_MIME_TYPES.has(contentType)) return null;
|
|
902
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
903
|
+
if (buffer.byteLength > MAX_THUMBNAIL_BYTES) return null;
|
|
904
|
+
return {
|
|
905
|
+
data: buffer.toString("base64"),
|
|
906
|
+
mimeType: contentType
|
|
907
|
+
};
|
|
908
|
+
} catch {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
838
912
|
function registerMediaTools(server, client) {
|
|
913
|
+
server.tool(
|
|
914
|
+
"get_assets",
|
|
915
|
+
"Get brand assets from the media library with visual thumbnails. Returns inline image previews plus full-resolution URLs for use in posts.",
|
|
916
|
+
{
|
|
917
|
+
limit: z4.number().optional().describe("Max number of assets to return with image previews. Default 10, max 20.")
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
title: "Get Brand Assets",
|
|
921
|
+
readOnlyHint: true,
|
|
922
|
+
destructiveHint: false,
|
|
923
|
+
idempotentHint: true,
|
|
924
|
+
openWorldHint: false
|
|
925
|
+
},
|
|
926
|
+
async ({ limit }) => {
|
|
927
|
+
const maxItems = Math.min(limit ?? 10, 20);
|
|
928
|
+
const result = await client.listMedia();
|
|
929
|
+
const items = result?.media ?? [];
|
|
930
|
+
const images = items.filter((m) => IMAGE_MIME_TYPES.has(m.mimeType)).slice(0, maxItems);
|
|
931
|
+
if (images.length === 0) {
|
|
932
|
+
return {
|
|
933
|
+
content: [
|
|
934
|
+
{
|
|
935
|
+
type: "text",
|
|
936
|
+
text: "No image assets found in the media library. Upload images with upload_from_url first."
|
|
937
|
+
}
|
|
938
|
+
]
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const thumbnails = await Promise.all(
|
|
942
|
+
images.map((img) => fetchImageAsBase64(img.url))
|
|
943
|
+
);
|
|
944
|
+
const content = [];
|
|
945
|
+
content.push({
|
|
946
|
+
type: "text",
|
|
947
|
+
text: `## Brand Assets (${images.length} images)
|
|
948
|
+
|
|
949
|
+
Use the URLs below when attaching images to posts or newsletters.`
|
|
950
|
+
});
|
|
951
|
+
for (let i = 0; i < images.length; i++) {
|
|
952
|
+
const img = images[i];
|
|
953
|
+
const thumb = thumbnails[i];
|
|
954
|
+
content.push({
|
|
955
|
+
type: "text",
|
|
956
|
+
text: `### ${img.filename}
|
|
957
|
+
- **URL:** ${img.url}
|
|
958
|
+
- **Type:** ${img.mimeType} | **Size:** ${(img.size / 1024).toFixed(1)}KB`
|
|
959
|
+
});
|
|
960
|
+
if (thumb) {
|
|
961
|
+
content.push({
|
|
962
|
+
type: "image",
|
|
963
|
+
data: thumb.data,
|
|
964
|
+
mimeType: thumb.mimeType
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return { content };
|
|
969
|
+
}
|
|
970
|
+
);
|
|
839
971
|
server.tool(
|
|
840
972
|
"upload_from_url",
|
|
841
973
|
"Upload media from a public URL to storage. Returns a CDN URL for use in posts.",
|
|
@@ -857,11 +989,9 @@ function registerMediaTools(server, client) {
|
|
|
857
989
|
filename: args.filename,
|
|
858
990
|
folder: args.folder
|
|
859
991
|
});
|
|
860
|
-
const item = result;
|
|
861
992
|
return {
|
|
862
993
|
content: [
|
|
863
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
864
|
-
...item.type === "image" && item.url && item.mimeType && !item.mimeType.startsWith("video/") ? [{ type: "image", url: item.url, mimeType: item.mimeType }] : []
|
|
994
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
865
995
|
]
|
|
866
996
|
};
|
|
867
997
|
}
|
|
@@ -879,12 +1009,9 @@ function registerMediaTools(server, client) {
|
|
|
879
1009
|
},
|
|
880
1010
|
async () => {
|
|
881
1011
|
const result = await client.listMedia();
|
|
882
|
-
const media = Array.isArray(result) ? result : result?.media ?? [];
|
|
883
|
-
const imageBlocks = media.filter((m) => m.type === "image" && m.url && m.mimeType && !m.mimeType.startsWith("video/")).map((m) => ({ type: "image", url: m.url, mimeType: m.mimeType }));
|
|
884
1012
|
return {
|
|
885
1013
|
content: [
|
|
886
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
887
|
-
...imageBlocks
|
|
1014
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
888
1015
|
]
|
|
889
1016
|
};
|
|
890
1017
|
}
|
|
@@ -922,7 +1049,37 @@ This will permanently delete this media file. Call this tool again with confirme
|
|
|
922
1049
|
|
|
923
1050
|
// src/tools/newsletter.ts
|
|
924
1051
|
import { z as z5 } from "zod";
|
|
1052
|
+
import {
|
|
1053
|
+
registerAppResource,
|
|
1054
|
+
registerAppTool,
|
|
1055
|
+
RESOURCE_MIME_TYPE
|
|
1056
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
1057
|
+
import * as fs from "fs";
|
|
1058
|
+
import * as path from "path";
|
|
1059
|
+
import { fileURLToPath } from "url";
|
|
1060
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1061
|
+
var NEWSLETTER_STUDIO_RESOURCE = "ui://newsletter-studio/app.html";
|
|
925
1062
|
function registerNewsletterTools(server, client, options = {}) {
|
|
1063
|
+
registerAppResource(
|
|
1064
|
+
server,
|
|
1065
|
+
NEWSLETTER_STUDIO_RESOURCE,
|
|
1066
|
+
NEWSLETTER_STUDIO_RESOURCE,
|
|
1067
|
+
{ mimeType: RESOURCE_MIME_TYPE },
|
|
1068
|
+
async () => {
|
|
1069
|
+
const distDir = __dirname.endsWith("dist") ? __dirname : path.resolve(__dirname, "..", "dist");
|
|
1070
|
+
const htmlPath = path.join(distDir, "newsletter-studio.html");
|
|
1071
|
+
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
1072
|
+
return {
|
|
1073
|
+
contents: [
|
|
1074
|
+
{
|
|
1075
|
+
uri: NEWSLETTER_STUDIO_RESOURCE,
|
|
1076
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
1077
|
+
text: html
|
|
1078
|
+
}
|
|
1079
|
+
]
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
926
1083
|
server.tool(
|
|
927
1084
|
"list_subscribers",
|
|
928
1085
|
"List email subscribers from the connected ESP.",
|
|
@@ -973,32 +1130,33 @@ function registerNewsletterTools(server, client, options = {}) {
|
|
|
973
1130
|
};
|
|
974
1131
|
}
|
|
975
1132
|
);
|
|
976
|
-
|
|
1133
|
+
registerAppTool(
|
|
1134
|
+
server,
|
|
977
1135
|
"create_newsletter",
|
|
978
|
-
"Push a newsletter to the user's ESP as a draft. Show an HTML preview to the user before calling this.",
|
|
979
|
-
{
|
|
980
|
-
subject: z5.string().describe("Email subject line"),
|
|
981
|
-
content: z5.string().describe("HTML content of the newsletter"),
|
|
982
|
-
preview_text: z5.string().optional().describe("Preview text shown in email clients")
|
|
983
|
-
},
|
|
984
1136
|
{
|
|
985
1137
|
title: "Create Newsletter Draft",
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1138
|
+
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.",
|
|
1139
|
+
inputSchema: {
|
|
1140
|
+
subject: z5.string().describe("Email subject line"),
|
|
1141
|
+
content: z5.string().describe("HTML content of the newsletter"),
|
|
1142
|
+
preview_text: z5.string().optional().describe("Preview text shown in email clients"),
|
|
1143
|
+
template_id: z5.string().optional().describe(
|
|
1144
|
+
"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."
|
|
1145
|
+
)
|
|
1146
|
+
},
|
|
1147
|
+
_meta: { ui: { resourceUri: NEWSLETTER_STUDIO_RESOURCE } }
|
|
990
1148
|
},
|
|
991
1149
|
async (args) => {
|
|
992
|
-
const
|
|
1150
|
+
const payload = {
|
|
993
1151
|
subject: args.subject,
|
|
994
1152
|
content: args.content,
|
|
995
1153
|
previewText: args.preview_text
|
|
996
|
-
}
|
|
1154
|
+
};
|
|
1155
|
+
if (args.template_id) payload.templateId = args.template_id;
|
|
1156
|
+
const result = await client.createBroadcast(payload);
|
|
997
1157
|
return {
|
|
998
1158
|
content: [
|
|
999
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1000
|
-
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1001
|
-
${args.content}` }] : []
|
|
1159
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1002
1160
|
]
|
|
1003
1161
|
};
|
|
1004
1162
|
}
|
|
@@ -1027,9 +1185,7 @@ ${args.content}` }] : []
|
|
|
1027
1185
|
const result = await client.updateBroadcast(args.broadcast_id, data);
|
|
1028
1186
|
return {
|
|
1029
1187
|
content: [
|
|
1030
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1031
|
-
...args.content ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
1032
|
-
${args.content}` }] : []
|
|
1188
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
1033
1189
|
]
|
|
1034
1190
|
};
|
|
1035
1191
|
}
|
|
@@ -1037,7 +1193,7 @@ ${args.content}` }] : []
|
|
|
1037
1193
|
if (options.allowDirectSend) {
|
|
1038
1194
|
server.tool(
|
|
1039
1195
|
"send_newsletter",
|
|
1040
|
-
"Send a newsletter to subscribers. This action cannot be undone.
|
|
1196
|
+
"Send a newsletter to subscribers. This action cannot be undone. Always set confirmed=false first \u2014 never send without explicit user approval.",
|
|
1041
1197
|
{
|
|
1042
1198
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to send"),
|
|
1043
1199
|
confirmed: z5.boolean().default(false).describe(
|
|
@@ -1101,7 +1257,7 @@ Call this tool again with confirmed=true to send.`;
|
|
|
1101
1257
|
}
|
|
1102
1258
|
server.tool(
|
|
1103
1259
|
"schedule_newsletter",
|
|
1104
|
-
"Schedule a newsletter for future send.
|
|
1260
|
+
"Schedule a newsletter for future send. Always set confirmed=false first \u2014 never schedule without explicit user approval.",
|
|
1105
1261
|
{
|
|
1106
1262
|
broadcast_id: z5.string().describe("The broadcast/newsletter ID to schedule"),
|
|
1107
1263
|
scheduled_for: z5.string().describe(
|
|
@@ -1411,24 +1567,6 @@ function registerNewsletterAdvancedTools(server, client) {
|
|
|
1411
1567
|
};
|
|
1412
1568
|
}
|
|
1413
1569
|
);
|
|
1414
|
-
server.tool(
|
|
1415
|
-
"get_post_aggregate_stats",
|
|
1416
|
-
"Get aggregate stats across all posts and newsletters.",
|
|
1417
|
-
{},
|
|
1418
|
-
{
|
|
1419
|
-
title: "Get Post Aggregate Stats",
|
|
1420
|
-
readOnlyHint: true,
|
|
1421
|
-
destructiveHint: false,
|
|
1422
|
-
idempotentHint: true,
|
|
1423
|
-
openWorldHint: true
|
|
1424
|
-
},
|
|
1425
|
-
async () => {
|
|
1426
|
-
const result = await client.getPostAggregateStats();
|
|
1427
|
-
return {
|
|
1428
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
);
|
|
1432
1570
|
server.tool(
|
|
1433
1571
|
"tag_subscriber",
|
|
1434
1572
|
"Add tags to a subscriber.",
|
|
@@ -1534,10 +1672,10 @@ import { z as z7 } from "zod";
|
|
|
1534
1672
|
function registerRssTools(server, client) {
|
|
1535
1673
|
server.tool(
|
|
1536
1674
|
"fetch_feed",
|
|
1537
|
-
"Fetch and parse entries from an RSS or Atom feed URL.",
|
|
1675
|
+
"Fetch and parse all available entries from an RSS or Atom feed URL. Returns up to 100 entries by default.",
|
|
1538
1676
|
{
|
|
1539
1677
|
url: z7.string().describe("The RSS/Atom feed URL to fetch"),
|
|
1540
|
-
limit: z7.number().optional().describe("Maximum number of entries to return (default
|
|
1678
|
+
limit: z7.number().optional().describe("Maximum number of entries to return (default 100)")
|
|
1541
1679
|
},
|
|
1542
1680
|
{
|
|
1543
1681
|
title: "Fetch RSS Feed",
|
|
@@ -1608,7 +1746,7 @@ import { z as z8 } from "zod";
|
|
|
1608
1746
|
function registerBrandVoiceTools(server, client) {
|
|
1609
1747
|
server.tool(
|
|
1610
1748
|
"get_brand_voice",
|
|
1611
|
-
"Get the brand voice profile and
|
|
1749
|
+
"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.",
|
|
1612
1750
|
{},
|
|
1613
1751
|
{
|
|
1614
1752
|
title: "Get Brand Voice",
|
|
@@ -1653,6 +1791,18 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1653
1791
|
lines.push("(none)");
|
|
1654
1792
|
}
|
|
1655
1793
|
lines.push("");
|
|
1794
|
+
lines.push("### Platform-Specific Examples");
|
|
1795
|
+
if (voice.platformExamples && Object.keys(voice.platformExamples).length > 0) {
|
|
1796
|
+
for (const [platform, examples] of Object.entries(voice.platformExamples)) {
|
|
1797
|
+
lines.push(`**${platform}:**`);
|
|
1798
|
+
examples.forEach((ex, i) => {
|
|
1799
|
+
lines.push(` ${i + 1}. ${ex}`);
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
} else {
|
|
1803
|
+
lines.push("(none)");
|
|
1804
|
+
}
|
|
1805
|
+
lines.push("");
|
|
1656
1806
|
lines.push("### Example Posts");
|
|
1657
1807
|
if (voice.examplePosts && voice.examplePosts.length > 0) {
|
|
1658
1808
|
voice.examplePosts.forEach((post, i) => {
|
|
@@ -1662,6 +1812,9 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1662
1812
|
lines.push("(none)");
|
|
1663
1813
|
}
|
|
1664
1814
|
lines.push("");
|
|
1815
|
+
if (voice.updatedAt) {
|
|
1816
|
+
lines.push(`_Last updated: ${voice.updatedAt}_`);
|
|
1817
|
+
}
|
|
1665
1818
|
return {
|
|
1666
1819
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
1667
1820
|
};
|
|
@@ -1690,7 +1843,8 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1690
1843
|
dos: z8.array(z8.string().max(200)).max(15).optional().describe("Writing rules to follow. Send the FULL list, not just additions."),
|
|
1691
1844
|
donts: z8.array(z8.string().max(200)).max(15).optional().describe("Things to avoid. Send the FULL list, not just additions."),
|
|
1692
1845
|
platform_rules: z8.record(z8.string().max(300)).optional().describe('Per-platform writing guidelines, e.g. { "twitter": "Keep under 200 chars" }'),
|
|
1693
|
-
|
|
1846
|
+
platform_examples: z8.record(z8.array(z8.string().max(500)).max(3)).optional().describe('Per-platform example posts, e.g. { "twitter": ["Example tweet 1", "Example tweet 2"], "linkedin": ["Example post"] }'),
|
|
1847
|
+
example_posts: z8.array(z8.string().max(500)).max(5).optional().describe("General example posts that demonstrate the desired voice"),
|
|
1694
1848
|
confirmed: z8.boolean().default(false).describe(
|
|
1695
1849
|
"Set to true to confirm and save. If false or missing, returns a preview of what will be saved."
|
|
1696
1850
|
)
|
|
@@ -1721,13 +1875,26 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1721
1875
|
}
|
|
1722
1876
|
payload.platformRules = args.platform_rules;
|
|
1723
1877
|
}
|
|
1878
|
+
if (args.platform_examples !== void 0) {
|
|
1879
|
+
const entries = Object.entries(args.platform_examples);
|
|
1880
|
+
if (entries.length > 6) {
|
|
1881
|
+
return {
|
|
1882
|
+
content: [{
|
|
1883
|
+
type: "text",
|
|
1884
|
+
text: "Maximum 6 platforms for examples allowed."
|
|
1885
|
+
}],
|
|
1886
|
+
isError: true
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
payload.platformExamples = args.platform_examples;
|
|
1890
|
+
}
|
|
1724
1891
|
if (args.example_posts !== void 0) payload.examplePosts = args.example_posts;
|
|
1725
1892
|
if (Object.keys(payload).length === 0) {
|
|
1726
1893
|
return {
|
|
1727
1894
|
content: [
|
|
1728
1895
|
{
|
|
1729
1896
|
type: "text",
|
|
1730
|
-
text: "No fields provided. Specify at least one field to update (name, description, dos, donts, platform_rules, example_posts)."
|
|
1897
|
+
text: "No fields provided. Specify at least one field to update (name, description, dos, donts, platform_rules, platform_examples, example_posts)."
|
|
1731
1898
|
}
|
|
1732
1899
|
],
|
|
1733
1900
|
isError: true
|
|
@@ -1770,6 +1937,17 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1770
1937
|
}
|
|
1771
1938
|
lines2.push("");
|
|
1772
1939
|
}
|
|
1940
|
+
if (payload.platformExamples) {
|
|
1941
|
+
const examples = payload.platformExamples;
|
|
1942
|
+
lines2.push(`### Platform-Specific Examples (${Object.keys(examples).length} platforms)`);
|
|
1943
|
+
for (const [platform, exList] of Object.entries(examples)) {
|
|
1944
|
+
lines2.push(`**${platform}:**`);
|
|
1945
|
+
exList.forEach((ex, i) => {
|
|
1946
|
+
lines2.push(` ${i + 1}. ${ex}`);
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
lines2.push("");
|
|
1950
|
+
}
|
|
1773
1951
|
if (payload.examplePosts) {
|
|
1774
1952
|
const posts = payload.examplePosts;
|
|
1775
1953
|
lines2.push(`### Example Posts (${posts.length})`);
|
|
@@ -1794,6 +1972,7 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1794
1972
|
if (payload.dos) summary.push(`${payload.dos.length} do's`);
|
|
1795
1973
|
if (payload.donts) summary.push(`${payload.donts.length} don'ts`);
|
|
1796
1974
|
if (payload.platformRules) summary.push(`${Object.keys(payload.platformRules).length} platform rules`);
|
|
1975
|
+
if (payload.platformExamples) summary.push(`${Object.keys(payload.platformExamples).length} platform example sets`);
|
|
1797
1976
|
if (payload.examplePosts) summary.push(`${payload.examplePosts.length} example posts`);
|
|
1798
1977
|
lines.push(`**Updated:** ${summary.join(", ")}`);
|
|
1799
1978
|
return {
|
|
@@ -1803,15 +1982,70 @@ function registerBrandVoiceTools(server, client) {
|
|
|
1803
1982
|
);
|
|
1804
1983
|
}
|
|
1805
1984
|
|
|
1806
|
-
// src/tools/
|
|
1985
|
+
// src/tools/audience.ts
|
|
1807
1986
|
import { z as z9 } from "zod";
|
|
1808
|
-
function
|
|
1987
|
+
function formatAudience(audience) {
|
|
1988
|
+
const lines = [];
|
|
1989
|
+
lines.push(`## ${audience.name}${audience.isDefault ? " (default)" : ""}`);
|
|
1990
|
+
lines.push("");
|
|
1991
|
+
if (audience.description) {
|
|
1992
|
+
lines.push("### Description");
|
|
1993
|
+
lines.push(audience.description);
|
|
1994
|
+
lines.push("");
|
|
1995
|
+
}
|
|
1996
|
+
if (audience.demographics) {
|
|
1997
|
+
lines.push("### Demographics");
|
|
1998
|
+
lines.push(audience.demographics);
|
|
1999
|
+
lines.push("");
|
|
2000
|
+
}
|
|
2001
|
+
if (audience.painPoints && audience.painPoints.length > 0) {
|
|
2002
|
+
lines.push("### Pain Points");
|
|
2003
|
+
for (const point of audience.painPoints) {
|
|
2004
|
+
lines.push(`- ${point}`);
|
|
2005
|
+
}
|
|
2006
|
+
lines.push("");
|
|
2007
|
+
}
|
|
2008
|
+
if (audience.motivations && audience.motivations.length > 0) {
|
|
2009
|
+
lines.push("### Motivations");
|
|
2010
|
+
for (const motivation of audience.motivations) {
|
|
2011
|
+
lines.push(`- ${motivation}`);
|
|
2012
|
+
}
|
|
2013
|
+
lines.push("");
|
|
2014
|
+
}
|
|
2015
|
+
if (audience.platforms && audience.platforms.length > 0) {
|
|
2016
|
+
lines.push("### Active Platforms");
|
|
2017
|
+
lines.push(audience.platforms.join(", "));
|
|
2018
|
+
lines.push("");
|
|
2019
|
+
}
|
|
2020
|
+
if (audience.toneNotes) {
|
|
2021
|
+
lines.push("### Tone Notes");
|
|
2022
|
+
lines.push(audience.toneNotes);
|
|
2023
|
+
lines.push("");
|
|
2024
|
+
}
|
|
2025
|
+
if (audience.contentPreferences) {
|
|
2026
|
+
lines.push("### Content Preferences");
|
|
2027
|
+
lines.push(audience.contentPreferences);
|
|
2028
|
+
lines.push("");
|
|
2029
|
+
}
|
|
2030
|
+
return lines.join("\n");
|
|
2031
|
+
}
|
|
2032
|
+
var audienceFields = {
|
|
2033
|
+
name: z9.string().max(100).describe("Audience name (must be unique per account)"),
|
|
2034
|
+
description: z9.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
2035
|
+
demographics: z9.string().max(500).optional().describe("Demographic details"),
|
|
2036
|
+
pain_points: z9.array(z9.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
2037
|
+
motivations: z9.array(z9.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
2038
|
+
preferred_platforms: z9.array(z9.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
2039
|
+
tone_notes: z9.string().max(500).optional().describe("How to speak to this audience"),
|
|
2040
|
+
content_preferences: z9.string().max(500).optional().describe("What content formats and topics resonate")
|
|
2041
|
+
};
|
|
2042
|
+
function registerAudienceTools(server, client) {
|
|
1809
2043
|
server.tool(
|
|
1810
|
-
"
|
|
1811
|
-
"
|
|
2044
|
+
"list_audiences",
|
|
2045
|
+
"List all audience profiles. Call this to see which audiences are available before creating content.",
|
|
1812
2046
|
{},
|
|
1813
2047
|
{
|
|
1814
|
-
title: "
|
|
2048
|
+
title: "List Audiences",
|
|
1815
2049
|
readOnlyHint: true,
|
|
1816
2050
|
destructiveHint: false,
|
|
1817
2051
|
idempotentHint: true,
|
|
@@ -1819,230 +2053,171 @@ function registerKnowledgeTools(server, client) {
|
|
|
1819
2053
|
},
|
|
1820
2054
|
async () => {
|
|
1821
2055
|
try {
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
if (items.length === 0) {
|
|
2056
|
+
const audiences = await client.listAudiences();
|
|
2057
|
+
if (!audiences || audiences.length === 0) {
|
|
1825
2058
|
return {
|
|
1826
2059
|
content: [
|
|
1827
2060
|
{
|
|
1828
2061
|
type: "text",
|
|
1829
|
-
text: "
|
|
2062
|
+
text: "No audience profiles configured. Use create_audience to add one."
|
|
1830
2063
|
}
|
|
1831
2064
|
]
|
|
1832
2065
|
};
|
|
1833
2066
|
}
|
|
1834
|
-
const totalChars = items.reduce(
|
|
1835
|
-
(sum, item) => sum + item.content.length,
|
|
1836
|
-
0
|
|
1837
|
-
);
|
|
1838
|
-
const shouldTruncate = totalChars > 1e4;
|
|
1839
2067
|
const lines = [];
|
|
1840
|
-
lines.push(`##
|
|
2068
|
+
lines.push(`## Audience Profiles (${audiences.length})`);
|
|
1841
2069
|
lines.push("");
|
|
1842
|
-
for (const
|
|
1843
|
-
lines.push(
|
|
1844
|
-
if (item.tags && item.tags.length > 0) {
|
|
1845
|
-
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1846
|
-
}
|
|
1847
|
-
if (shouldTruncate) {
|
|
1848
|
-
lines.push(item.content.slice(0, 500) + " [truncated]");
|
|
1849
|
-
} else {
|
|
1850
|
-
lines.push(item.content);
|
|
1851
|
-
}
|
|
1852
|
-
lines.push("");
|
|
2070
|
+
for (const a of audiences) {
|
|
2071
|
+
lines.push(`- **${a.name}**${a.isDefault ? " (default)" : ""}: ${a.description || "(no description)"}`);
|
|
1853
2072
|
}
|
|
2073
|
+
lines.push("");
|
|
2074
|
+
lines.push("Use get_audience with a name to see the full profile.");
|
|
1854
2075
|
return {
|
|
1855
2076
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
1856
2077
|
};
|
|
1857
2078
|
} catch (error) {
|
|
1858
2079
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1859
|
-
throw new Error(`Failed to
|
|
2080
|
+
throw new Error(`Failed to list audiences: ${message}`);
|
|
1860
2081
|
}
|
|
1861
2082
|
}
|
|
1862
2083
|
);
|
|
1863
2084
|
server.tool(
|
|
1864
|
-
"
|
|
1865
|
-
"
|
|
2085
|
+
"get_audience",
|
|
2086
|
+
"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.",
|
|
1866
2087
|
{
|
|
1867
|
-
|
|
1868
|
-
"Search query - matches against tags first, then falls back to text search on title and content"
|
|
1869
|
-
)
|
|
2088
|
+
name: z9.string().optional().describe("Audience name to look up. If omitted, returns the default audience.")
|
|
1870
2089
|
},
|
|
1871
2090
|
{
|
|
1872
|
-
title: "
|
|
2091
|
+
title: "Get Audience Profile",
|
|
1873
2092
|
readOnlyHint: true,
|
|
1874
2093
|
destructiveHint: false,
|
|
1875
2094
|
idempotentHint: true,
|
|
1876
2095
|
openWorldHint: false
|
|
1877
2096
|
},
|
|
1878
|
-
async ({
|
|
2097
|
+
async ({ name }) => {
|
|
1879
2098
|
try {
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2099
|
+
let audience;
|
|
2100
|
+
if (name) {
|
|
2101
|
+
audience = await client.getAudienceByName(name);
|
|
2102
|
+
} else {
|
|
2103
|
+
audience = await client.getDefaultAudience();
|
|
2104
|
+
}
|
|
2105
|
+
return {
|
|
2106
|
+
content: [{ type: "text", text: formatAudience(audience) }]
|
|
2107
|
+
};
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2110
|
+
if (message.includes("404") || message.includes("No audience") || message.includes("not found")) {
|
|
1883
2111
|
return {
|
|
1884
2112
|
content: [
|
|
1885
2113
|
{
|
|
1886
2114
|
type: "text",
|
|
1887
|
-
text: `No
|
|
2115
|
+
text: name ? `No audience named "${name}" found. Use list_audiences to see available profiles.` : "No audience profiles configured. Use create_audience to add one."
|
|
1888
2116
|
}
|
|
1889
2117
|
]
|
|
1890
2118
|
};
|
|
1891
2119
|
}
|
|
1892
|
-
|
|
1893
|
-
lines.push(
|
|
1894
|
-
`## Knowledge Base Results for "${query}" (${items.length} items)`
|
|
1895
|
-
);
|
|
1896
|
-
lines.push("");
|
|
1897
|
-
for (const item of items) {
|
|
1898
|
-
lines.push(`### ${item.title}`);
|
|
1899
|
-
if (item.tags && item.tags.length > 0) {
|
|
1900
|
-
lines.push(`Tags: ${item.tags.join(", ")}`);
|
|
1901
|
-
}
|
|
1902
|
-
lines.push(item.content);
|
|
1903
|
-
lines.push("");
|
|
1904
|
-
}
|
|
1905
|
-
return {
|
|
1906
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
1907
|
-
};
|
|
1908
|
-
} catch (error) {
|
|
1909
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1910
|
-
throw new Error(`Failed to search knowledge base: ${message}`);
|
|
2120
|
+
throw error;
|
|
1911
2121
|
}
|
|
1912
2122
|
}
|
|
1913
2123
|
);
|
|
1914
2124
|
server.tool(
|
|
1915
|
-
"
|
|
1916
|
-
"
|
|
2125
|
+
"create_audience",
|
|
2126
|
+
"Create a new audience profile. Set confirmed=false to preview first.",
|
|
1917
2127
|
{
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2128
|
+
...audienceFields,
|
|
2129
|
+
is_default: z9.boolean().optional().describe("Set as the default audience profile"),
|
|
2130
|
+
confirmed: z9.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
1921
2131
|
},
|
|
1922
2132
|
{
|
|
1923
|
-
title: "
|
|
2133
|
+
title: "Create Audience",
|
|
1924
2134
|
readOnlyHint: false,
|
|
1925
2135
|
destructiveHint: false,
|
|
1926
2136
|
idempotentHint: false,
|
|
1927
2137
|
openWorldHint: false
|
|
1928
2138
|
},
|
|
1929
|
-
async (
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
type: "text",
|
|
1941
|
-
text: `Added "${title}" to the knowledge base.`
|
|
1942
|
-
}
|
|
1943
|
-
]
|
|
1944
|
-
};
|
|
1945
|
-
} catch (error) {
|
|
1946
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1947
|
-
throw new Error(`Failed to add knowledge item: ${message}`);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
);
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
// src/tools/audience.ts
|
|
1954
|
-
import { z as z10 } from "zod";
|
|
1955
|
-
function registerAudienceTools(server, client) {
|
|
1956
|
-
server.tool(
|
|
1957
|
-
"get_audience",
|
|
1958
|
-
"Get the target audience profile. Call before creating content.",
|
|
1959
|
-
{},
|
|
1960
|
-
{
|
|
1961
|
-
title: "Get Audience Profile",
|
|
1962
|
-
readOnlyHint: true,
|
|
1963
|
-
destructiveHint: false,
|
|
1964
|
-
idempotentHint: true,
|
|
1965
|
-
openWorldHint: false
|
|
1966
|
-
},
|
|
1967
|
-
async () => {
|
|
1968
|
-
try {
|
|
1969
|
-
const audience = await client.getDefaultAudience();
|
|
2139
|
+
async (args) => {
|
|
2140
|
+
const payload = { name: args.name };
|
|
2141
|
+
if (args.description !== void 0) payload.description = args.description;
|
|
2142
|
+
if (args.demographics !== void 0) payload.demographics = args.demographics;
|
|
2143
|
+
if (args.pain_points !== void 0) payload.painPoints = args.pain_points;
|
|
2144
|
+
if (args.motivations !== void 0) payload.motivations = args.motivations;
|
|
2145
|
+
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
2146
|
+
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
2147
|
+
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2148
|
+
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
2149
|
+
if (args.confirmed !== true) {
|
|
1970
2150
|
const lines = [];
|
|
1971
|
-
lines.push(
|
|
2151
|
+
lines.push("## New Audience Preview");
|
|
1972
2152
|
lines.push("");
|
|
1973
|
-
|
|
2153
|
+
lines.push(`**Name:** ${args.name}`);
|
|
2154
|
+
if (args.is_default) lines.push("**Default:** yes");
|
|
2155
|
+
lines.push("");
|
|
2156
|
+
if (payload.description) {
|
|
1974
2157
|
lines.push("### Description");
|
|
1975
|
-
lines.push(
|
|
2158
|
+
lines.push(String(payload.description));
|
|
1976
2159
|
lines.push("");
|
|
1977
2160
|
}
|
|
1978
|
-
if (
|
|
2161
|
+
if (payload.demographics) {
|
|
1979
2162
|
lines.push("### Demographics");
|
|
1980
|
-
lines.push(
|
|
2163
|
+
lines.push(String(payload.demographics));
|
|
1981
2164
|
lines.push("");
|
|
1982
2165
|
}
|
|
1983
|
-
if (
|
|
1984
|
-
lines.push(
|
|
1985
|
-
for (const
|
|
1986
|
-
lines.push(`- ${point}`);
|
|
1987
|
-
}
|
|
2166
|
+
if (payload.painPoints) {
|
|
2167
|
+
lines.push(`### Pain Points (${payload.painPoints.length})`);
|
|
2168
|
+
for (const p of payload.painPoints) lines.push(`- ${p}`);
|
|
1988
2169
|
lines.push("");
|
|
1989
2170
|
}
|
|
1990
|
-
if (
|
|
1991
|
-
lines.push(
|
|
1992
|
-
for (const
|
|
1993
|
-
lines.push(`- ${motivation}`);
|
|
1994
|
-
}
|
|
2171
|
+
if (payload.motivations) {
|
|
2172
|
+
lines.push(`### Motivations (${payload.motivations.length})`);
|
|
2173
|
+
for (const m of payload.motivations) lines.push(`- ${m}`);
|
|
1995
2174
|
lines.push("");
|
|
1996
2175
|
}
|
|
1997
|
-
if (
|
|
1998
|
-
lines.push(
|
|
1999
|
-
lines.push(
|
|
2176
|
+
if (payload.platforms) {
|
|
2177
|
+
lines.push(`### Platforms`);
|
|
2178
|
+
lines.push(payload.platforms.join(", "));
|
|
2000
2179
|
lines.push("");
|
|
2001
2180
|
}
|
|
2002
|
-
if (
|
|
2181
|
+
if (payload.toneNotes) {
|
|
2003
2182
|
lines.push("### Tone Notes");
|
|
2004
|
-
lines.push(
|
|
2183
|
+
lines.push(String(payload.toneNotes));
|
|
2005
2184
|
lines.push("");
|
|
2006
2185
|
}
|
|
2007
|
-
if (
|
|
2186
|
+
if (payload.contentPreferences) {
|
|
2008
2187
|
lines.push("### Content Preferences");
|
|
2009
|
-
lines.push(
|
|
2188
|
+
lines.push(String(payload.contentPreferences));
|
|
2010
2189
|
lines.push("");
|
|
2011
2190
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
};
|
|
2015
|
-
} catch (error) {
|
|
2016
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2017
|
-
if (message.includes("404") || message.includes("No audience")) {
|
|
2018
|
-
return {
|
|
2019
|
-
content: [
|
|
2020
|
-
{
|
|
2021
|
-
type: "text",
|
|
2022
|
-
text: "No audience profile has been configured yet. The customer can set one up at their BuzzPoster dashboard under Audiences."
|
|
2023
|
-
}
|
|
2024
|
-
]
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
|
-
throw error;
|
|
2191
|
+
lines.push("---");
|
|
2192
|
+
lines.push("Call this tool again with **confirmed=true** to save.");
|
|
2193
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2028
2194
|
}
|
|
2195
|
+
const result = await client.createAudience(payload);
|
|
2196
|
+
return {
|
|
2197
|
+
content: [
|
|
2198
|
+
{
|
|
2199
|
+
type: "text",
|
|
2200
|
+
text: `Audience "${result.name}" created successfully (ID: ${result.id}).`
|
|
2201
|
+
}
|
|
2202
|
+
]
|
|
2203
|
+
};
|
|
2029
2204
|
}
|
|
2030
2205
|
);
|
|
2031
2206
|
server.tool(
|
|
2032
2207
|
"update_audience",
|
|
2033
|
-
"Update
|
|
2034
|
-
{
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
)
|
|
2208
|
+
"Update an existing audience profile by name. Set confirmed=false to preview first.",
|
|
2209
|
+
{
|
|
2210
|
+
audience_name: z9.string().describe("Name of the audience to update"),
|
|
2211
|
+
name: z9.string().max(100).optional().describe("New name for the audience"),
|
|
2212
|
+
description: z9.string().max(500).optional().describe("Brief description of who this audience is"),
|
|
2213
|
+
demographics: z9.string().max(500).optional().describe("Demographic details"),
|
|
2214
|
+
pain_points: z9.array(z9.string().max(200)).max(10).optional().describe("Problems the audience faces. Send the FULL list."),
|
|
2215
|
+
motivations: z9.array(z9.string().max(200)).max(10).optional().describe("Goals and desires. Send the FULL list."),
|
|
2216
|
+
preferred_platforms: z9.array(z9.string()).max(6).optional().describe("Social platforms the audience is most active on. Send the FULL list."),
|
|
2217
|
+
tone_notes: z9.string().max(500).optional().describe("How to speak to this audience"),
|
|
2218
|
+
content_preferences: z9.string().max(500).optional().describe("What content formats and topics resonate"),
|
|
2219
|
+
is_default: z9.boolean().optional().describe("Set as the default audience profile"),
|
|
2220
|
+
confirmed: z9.boolean().default(false).describe("Set to true to confirm and save. If false, returns a preview.")
|
|
2046
2221
|
},
|
|
2047
2222
|
{
|
|
2048
2223
|
title: "Update Audience",
|
|
@@ -2052,6 +2227,20 @@ function registerAudienceTools(server, client) {
|
|
|
2052
2227
|
openWorldHint: false
|
|
2053
2228
|
},
|
|
2054
2229
|
async (args) => {
|
|
2230
|
+
let target;
|
|
2231
|
+
try {
|
|
2232
|
+
target = await client.getAudienceByName(args.audience_name);
|
|
2233
|
+
} catch {
|
|
2234
|
+
return {
|
|
2235
|
+
content: [
|
|
2236
|
+
{
|
|
2237
|
+
type: "text",
|
|
2238
|
+
text: `Audience "${args.audience_name}" not found. Use list_audiences to see available profiles.`
|
|
2239
|
+
}
|
|
2240
|
+
],
|
|
2241
|
+
isError: true
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2055
2244
|
const payload = {};
|
|
2056
2245
|
if (args.name !== void 0) payload.name = args.name;
|
|
2057
2246
|
if (args.description !== void 0) payload.description = args.description;
|
|
@@ -2061,661 +2250,187 @@ function registerAudienceTools(server, client) {
|
|
|
2061
2250
|
if (args.preferred_platforms !== void 0) payload.platforms = args.preferred_platforms;
|
|
2062
2251
|
if (args.tone_notes !== void 0) payload.toneNotes = args.tone_notes;
|
|
2063
2252
|
if (args.content_preferences !== void 0) payload.contentPreferences = args.content_preferences;
|
|
2253
|
+
if (args.is_default !== void 0) payload.isDefault = args.is_default;
|
|
2064
2254
|
if (Object.keys(payload).length === 0) {
|
|
2065
2255
|
return {
|
|
2066
|
-
content: [
|
|
2067
|
-
{
|
|
2068
|
-
type: "text",
|
|
2069
|
-
text: "No fields provided. Specify at least one field to update."
|
|
2070
|
-
}
|
|
2071
|
-
],
|
|
2072
|
-
isError: true
|
|
2073
|
-
};
|
|
2074
|
-
}
|
|
2075
|
-
let targetId;
|
|
2076
|
-
let isCreating = false;
|
|
2077
|
-
try {
|
|
2078
|
-
const defaultAudience = await client.getDefaultAudience();
|
|
2079
|
-
targetId = String(defaultAudience.id);
|
|
2080
|
-
} catch {
|
|
2081
|
-
isCreating = true;
|
|
2082
|
-
}
|
|
2083
|
-
if (isCreating && !payload.name) {
|
|
2084
|
-
return {
|
|
2085
|
-
content: [
|
|
2086
|
-
{
|
|
2087
|
-
type: "text",
|
|
2088
|
-
text: "No audience exists yet. Provide a **name** to create one (e.g. name: 'SaaS Founders')."
|
|
2089
|
-
}
|
|
2090
|
-
],
|
|
2256
|
+
content: [{ type: "text", text: "No fields provided to update." }],
|
|
2091
2257
|
isError: true
|
|
2092
2258
|
};
|
|
2093
2259
|
}
|
|
2094
2260
|
if (args.confirmed !== true) {
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2261
|
+
const lines = [];
|
|
2262
|
+
lines.push(`## Update Audience "${args.audience_name}" Preview`);
|
|
2263
|
+
lines.push("");
|
|
2098
2264
|
if (payload.name) {
|
|
2099
|
-
|
|
2100
|
-
|
|
2265
|
+
lines.push(`**New Name:** ${payload.name}`);
|
|
2266
|
+
lines.push("");
|
|
2101
2267
|
}
|
|
2102
2268
|
if (payload.description) {
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2269
|
+
lines.push("### Description");
|
|
2270
|
+
lines.push(String(payload.description));
|
|
2271
|
+
lines.push("");
|
|
2106
2272
|
}
|
|
2107
2273
|
if (payload.demographics) {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
lines2.push("");
|
|
2111
|
-
}
|
|
2112
|
-
if (payload.painPoints) {
|
|
2113
|
-
const points = payload.painPoints;
|
|
2114
|
-
lines2.push(`### Pain Points (${points.length})`);
|
|
2115
|
-
for (const point of points) {
|
|
2116
|
-
lines2.push(`- ${point}`);
|
|
2117
|
-
}
|
|
2118
|
-
lines2.push("");
|
|
2119
|
-
}
|
|
2120
|
-
if (payload.motivations) {
|
|
2121
|
-
const motivations = payload.motivations;
|
|
2122
|
-
lines2.push(`### Motivations (${motivations.length})`);
|
|
2123
|
-
for (const motivation of motivations) {
|
|
2124
|
-
lines2.push(`- ${motivation}`);
|
|
2125
|
-
}
|
|
2126
|
-
lines2.push("");
|
|
2127
|
-
}
|
|
2128
|
-
if (payload.platforms) {
|
|
2129
|
-
const platforms = payload.platforms;
|
|
2130
|
-
lines2.push(`### Preferred Platforms (${platforms.length})`);
|
|
2131
|
-
lines2.push(platforms.join(", "));
|
|
2132
|
-
lines2.push("");
|
|
2133
|
-
}
|
|
2134
|
-
if (payload.toneNotes) {
|
|
2135
|
-
lines2.push("### Tone Notes");
|
|
2136
|
-
lines2.push(String(payload.toneNotes));
|
|
2137
|
-
lines2.push("");
|
|
2138
|
-
}
|
|
2139
|
-
if (payload.contentPreferences) {
|
|
2140
|
-
lines2.push("### Content Preferences");
|
|
2141
|
-
lines2.push(String(payload.contentPreferences));
|
|
2142
|
-
lines2.push("");
|
|
2143
|
-
}
|
|
2144
|
-
lines2.push("---");
|
|
2145
|
-
lines2.push("Call this tool again with **confirmed=true** to save these changes.");
|
|
2146
|
-
return {
|
|
2147
|
-
content: [{ type: "text", text: lines2.join("\n") }]
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
let result;
|
|
2151
|
-
if (isCreating) {
|
|
2152
|
-
if (payload.isDefault === void 0) payload.isDefault = true;
|
|
2153
|
-
result = await client.createAudience(payload);
|
|
2154
|
-
} else {
|
|
2155
|
-
result = await client.updateAudience(targetId, payload);
|
|
2156
|
-
}
|
|
2157
|
-
const lines = [];
|
|
2158
|
-
lines.push(`Audience "${result.name}" has been ${isCreating ? "created" : "updated"} successfully.`);
|
|
2159
|
-
lines.push("");
|
|
2160
|
-
const summary = [];
|
|
2161
|
-
if (payload.name) summary.push("name");
|
|
2162
|
-
if (payload.description) summary.push("description");
|
|
2163
|
-
if (payload.demographics) summary.push("demographics");
|
|
2164
|
-
if (payload.painPoints) summary.push(`${payload.painPoints.length} pain points`);
|
|
2165
|
-
if (payload.motivations) summary.push(`${payload.motivations.length} motivations`);
|
|
2166
|
-
if (payload.platforms) summary.push(`${payload.platforms.length} platforms`);
|
|
2167
|
-
if (payload.toneNotes) summary.push("tone notes");
|
|
2168
|
-
if (payload.contentPreferences) summary.push("content preferences");
|
|
2169
|
-
lines.push(`**${isCreating ? "Created with" : "Updated"}:** ${summary.join(", ")}`);
|
|
2170
|
-
return {
|
|
2171
|
-
content: [{ type: "text", text: lines.join("\n") }]
|
|
2172
|
-
};
|
|
2173
|
-
}
|
|
2174
|
-
);
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
// src/tools/newsletter-template.ts
|
|
2178
|
-
import { z as z11 } from "zod";
|
|
2179
|
-
var NEWSLETTER_CRAFT_GUIDE = `## Newsletter Craft Guide
|
|
2180
|
-
|
|
2181
|
-
Follow these rules when drafting:
|
|
2182
|
-
|
|
2183
|
-
### HTML Email Rules
|
|
2184
|
-
- Use table-based layouts, inline CSS only, 600px max width
|
|
2185
|
-
- Email-safe fonts only: Arial, Helvetica, Georgia, Verdana
|
|
2186
|
-
- Keep total email under 102KB (Gmail clips larger)
|
|
2187
|
-
- All images must use absolute URLs hosted on the customer's R2 CDN
|
|
2188
|
-
- Include alt text on every image
|
|
2189
|
-
- Use role="presentation" on layout tables
|
|
2190
|
-
- No JavaScript, no forms, no CSS grid/flexbox, no background images
|
|
2191
|
-
|
|
2192
|
-
### Structure
|
|
2193
|
-
- Subject line: 6-10 words, specific not clickbaity
|
|
2194
|
-
- Preview text: complements the subject, doesn't repeat it
|
|
2195
|
-
- Open strong -- lead with the most interesting thing
|
|
2196
|
-
- One clear CTA per newsletter
|
|
2197
|
-
- Sign off should feel human, not corporate
|
|
2198
|
-
|
|
2199
|
-
### Personalization
|
|
2200
|
-
- Kit: use {{ subscriber.first_name }}
|
|
2201
|
-
- Beehiiv: use {{email}} or {{subscriber_id}}
|
|
2202
|
-
- Mailchimp: use *NAME|*
|
|
2203
|
-
|
|
2204
|
-
### Images
|
|
2205
|
-
- Upload to customer's R2 CDN via upload_media or upload_from_url
|
|
2206
|
-
- Reference with full CDN URLs in img tags
|
|
2207
|
-
- Always set width, height, and alt attributes
|
|
2208
|
-
- Use style="display:block;" to prevent phantom spacing`;
|
|
2209
|
-
function registerNewsletterTemplateTools(server, client) {
|
|
2210
|
-
server.tool(
|
|
2211
|
-
"get_newsletter_template",
|
|
2212
|
-
"Get a newsletter template's structure, sections, and HTML. Pass templateId or omit for default.",
|
|
2213
|
-
{
|
|
2214
|
-
templateId: z11.string().optional().describe(
|
|
2215
|
-
"Specific template ID. If omitted, returns the default template."
|
|
2216
|
-
)
|
|
2217
|
-
},
|
|
2218
|
-
{
|
|
2219
|
-
title: "Get Newsletter Template",
|
|
2220
|
-
readOnlyHint: true,
|
|
2221
|
-
destructiveHint: false,
|
|
2222
|
-
idempotentHint: true,
|
|
2223
|
-
openWorldHint: false
|
|
2224
|
-
},
|
|
2225
|
-
async ({ templateId }) => {
|
|
2226
|
-
try {
|
|
2227
|
-
const template = await client.getTemplate(templateId);
|
|
2228
|
-
const lines = [];
|
|
2229
|
-
lines.push(`## Newsletter Template: ${template.name}`);
|
|
2230
|
-
if (template.description) lines.push(template.description);
|
|
2231
|
-
lines.push("");
|
|
2232
|
-
if (template.audienceId)
|
|
2233
|
-
lines.push(`Audience ID: ${template.audienceId}`);
|
|
2234
|
-
if (template.sendCadence)
|
|
2235
|
-
lines.push(`Cadence: ${template.sendCadence}`);
|
|
2236
|
-
if (template.subjectPattern)
|
|
2237
|
-
lines.push(`Subject pattern: ${template.subjectPattern}`);
|
|
2238
|
-
if (template.previewTextPattern)
|
|
2239
|
-
lines.push(`Preview text pattern: ${template.previewTextPattern}`);
|
|
2240
|
-
lines.push("");
|
|
2241
|
-
if (template.sections && template.sections.length > 0) {
|
|
2242
|
-
lines.push("### Sections (in order):");
|
|
2274
|
+
lines.push("### Demographics");
|
|
2275
|
+
lines.push(String(payload.demographics));
|
|
2243
2276
|
lines.push("");
|
|
2244
|
-
for (let i = 0; i < template.sections.length; i++) {
|
|
2245
|
-
const s = template.sections[i];
|
|
2246
|
-
lines.push(`**${i + 1}. ${s.label}** (${s.type})`);
|
|
2247
|
-
if (s.instructions)
|
|
2248
|
-
lines.push(`Instructions: ${s.instructions}`);
|
|
2249
|
-
if (s.word_count)
|
|
2250
|
-
lines.push(
|
|
2251
|
-
`Word count: ${s.word_count.min}-${s.word_count.max} words`
|
|
2252
|
-
);
|
|
2253
|
-
if (s.tone_override) lines.push(`Tone: ${s.tone_override}`);
|
|
2254
|
-
if (s.content_source) {
|
|
2255
|
-
const src = s.content_source;
|
|
2256
|
-
if (src.type === "rss" && src.feed_urls?.length)
|
|
2257
|
-
lines.push(`Content source: RSS - ${src.feed_urls.join(", ")}`);
|
|
2258
|
-
else if (src.type === "knowledge" && src.knowledge_item_ids?.length)
|
|
2259
|
-
lines.push(
|
|
2260
|
-
`Content source: Knowledge items ${src.knowledge_item_ids.join(", ")}`
|
|
2261
|
-
);
|
|
2262
|
-
else lines.push(`Content source: ${src.type}`);
|
|
2263
|
-
}
|
|
2264
|
-
if (s.count) lines.push(`Count: ${s.count} items`);
|
|
2265
|
-
lines.push(
|
|
2266
|
-
`Required: ${s.required !== false ? "yes" : "no"}`
|
|
2267
|
-
);
|
|
2268
|
-
if (s.type === "cta") {
|
|
2269
|
-
if (s.button_text) lines.push(`Button text: ${s.button_text}`);
|
|
2270
|
-
if (s.button_url) lines.push(`Button URL: ${s.button_url}`);
|
|
2271
|
-
}
|
|
2272
|
-
if (s.type === "sponsor") {
|
|
2273
|
-
if (s.sponsor_name)
|
|
2274
|
-
lines.push(`Sponsor: ${s.sponsor_name}`);
|
|
2275
|
-
if (s.talking_points)
|
|
2276
|
-
lines.push(`Talking points: ${s.talking_points}`);
|
|
2277
|
-
}
|
|
2278
|
-
if (s.type === "header") {
|
|
2279
|
-
if (s.tagline) lines.push(`Tagline: ${s.tagline}`);
|
|
2280
|
-
}
|
|
2281
|
-
if (s.type === "signoff") {
|
|
2282
|
-
if (s.author_name)
|
|
2283
|
-
lines.push(`Author: ${s.author_name}`);
|
|
2284
|
-
if (s.author_title)
|
|
2285
|
-
lines.push(`Title: ${s.author_title}`);
|
|
2286
|
-
}
|
|
2287
|
-
lines.push("");
|
|
2288
|
-
}
|
|
2289
2277
|
}
|
|
2290
|
-
if (
|
|
2291
|
-
lines.push(
|
|
2292
|
-
const
|
|
2293
|
-
if (st.primary_color) lines.push(`Primary color: ${st.primary_color}`);
|
|
2294
|
-
if (st.accent_color) lines.push(`Accent color: ${st.accent_color}`);
|
|
2295
|
-
if (st.font_family) lines.push(`Font: ${st.font_family}`);
|
|
2296
|
-
if (st.heading_font) lines.push(`Heading font: ${st.heading_font}`);
|
|
2297
|
-
if (st.content_width)
|
|
2298
|
-
lines.push(`Content width: ${st.content_width}px`);
|
|
2299
|
-
if (st.text_color) lines.push(`Text color: ${st.text_color}`);
|
|
2300
|
-
if (st.link_color) lines.push(`Link color: ${st.link_color}`);
|
|
2301
|
-
if (st.background_color)
|
|
2302
|
-
lines.push(`Background: ${st.background_color}`);
|
|
2303
|
-
if (st.button_style)
|
|
2304
|
-
lines.push(
|
|
2305
|
-
`Button style: ${st.button_style.color} text on ${st.button_style.text_color}, ${st.button_style.shape}`
|
|
2306
|
-
);
|
|
2307
|
-
lines.push("");
|
|
2308
|
-
lines.push(
|
|
2309
|
-
"NOTE: Apply these style values as inline CSS when generating the newsletter HTML."
|
|
2310
|
-
);
|
|
2278
|
+
if (payload.painPoints) {
|
|
2279
|
+
lines.push(`### Pain Points (${payload.painPoints.length})`);
|
|
2280
|
+
for (const p of payload.painPoints) lines.push(`- ${p}`);
|
|
2311
2281
|
lines.push("");
|
|
2312
2282
|
}
|
|
2313
|
-
if (
|
|
2314
|
-
lines.push(
|
|
2315
|
-
|
|
2316
|
-
lines.push("");
|
|
2317
|
-
lines.push("```html");
|
|
2318
|
-
lines.push(template.htmlContent);
|
|
2319
|
-
lines.push("```");
|
|
2283
|
+
if (payload.motivations) {
|
|
2284
|
+
lines.push(`### Motivations (${payload.motivations.length})`);
|
|
2285
|
+
for (const m of payload.motivations) lines.push(`- ${m}`);
|
|
2320
2286
|
lines.push("");
|
|
2321
2287
|
}
|
|
2322
|
-
if (
|
|
2323
|
-
lines.push(
|
|
2324
|
-
|
|
2325
|
-
lines.push(`- ${url}`);
|
|
2326
|
-
}
|
|
2288
|
+
if (payload.platforms) {
|
|
2289
|
+
lines.push(`### Platforms`);
|
|
2290
|
+
lines.push(payload.platforms.join(", "));
|
|
2327
2291
|
lines.push("");
|
|
2328
2292
|
}
|
|
2329
|
-
if (
|
|
2330
|
-
lines.push("###
|
|
2331
|
-
lines.push(
|
|
2332
|
-
`Knowledge item IDs: ${template.knowledgeItemIds.join(", ")}`
|
|
2333
|
-
);
|
|
2293
|
+
if (payload.toneNotes) {
|
|
2294
|
+
lines.push("### Tone Notes");
|
|
2295
|
+
lines.push(String(payload.toneNotes));
|
|
2334
2296
|
lines.push("");
|
|
2335
2297
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
};
|
|
2340
|
-
} catch (error) {
|
|
2341
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2342
|
-
if (message.includes("404") || message.includes("No newsletter templates")) {
|
|
2343
|
-
return {
|
|
2344
|
-
content: [
|
|
2345
|
-
{
|
|
2346
|
-
type: "text",
|
|
2347
|
-
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
|
|
2348
|
-
}
|
|
2349
|
-
]
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
throw error;
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
);
|
|
2356
|
-
server.tool(
|
|
2357
|
-
"get_past_newsletters",
|
|
2358
|
-
"Get past newsletters from the archive. Pass newsletterId to get full content of a specific edition.",
|
|
2359
|
-
{
|
|
2360
|
-
newsletterId: z11.string().optional().describe("If provided, returns the full content of this specific newsletter. If omitted, returns the list."),
|
|
2361
|
-
limit: z11.number().optional().describe("Number of past newsletters to retrieve (when listing). Default 5, max 50."),
|
|
2362
|
-
templateId: z11.string().optional().describe("Filter by template ID to see past newsletters from a specific template.")
|
|
2363
|
-
},
|
|
2364
|
-
{
|
|
2365
|
-
title: "Get Past Newsletters",
|
|
2366
|
-
readOnlyHint: true,
|
|
2367
|
-
destructiveHint: false,
|
|
2368
|
-
idempotentHint: true,
|
|
2369
|
-
openWorldHint: false
|
|
2370
|
-
},
|
|
2371
|
-
async ({ newsletterId, limit, templateId }) => {
|
|
2372
|
-
if (newsletterId) {
|
|
2373
|
-
try {
|
|
2374
|
-
const newsletter = await client.getArchivedNewsletter(
|
|
2375
|
-
newsletterId
|
|
2376
|
-
);
|
|
2377
|
-
const lines = [];
|
|
2378
|
-
lines.push(`## ${newsletter.subject}`);
|
|
2379
|
-
if (newsletter.sentAt) lines.push(`Sent: ${newsletter.sentAt}`);
|
|
2380
|
-
if (newsletter.notes) lines.push(`Notes: ${newsletter.notes}`);
|
|
2298
|
+
if (payload.contentPreferences) {
|
|
2299
|
+
lines.push("### Content Preferences");
|
|
2300
|
+
lines.push(String(payload.contentPreferences));
|
|
2381
2301
|
lines.push("");
|
|
2382
|
-
lines.push("### Full HTML Content:");
|
|
2383
|
-
lines.push(newsletter.contentHtml);
|
|
2384
|
-
return {
|
|
2385
|
-
content: [
|
|
2386
|
-
{ type: "text", text: lines.join("\n") },
|
|
2387
|
-
...newsletter.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2388
|
-
${newsletter.contentHtml}` }] : []
|
|
2389
|
-
]
|
|
2390
|
-
};
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2393
|
-
if (message.includes("404")) {
|
|
2394
|
-
return {
|
|
2395
|
-
content: [
|
|
2396
|
-
{
|
|
2397
|
-
type: "text",
|
|
2398
|
-
text: "Newsletter not found. Check the ID and try again."
|
|
2399
|
-
}
|
|
2400
|
-
]
|
|
2401
|
-
};
|
|
2402
|
-
}
|
|
2403
|
-
throw error;
|
|
2404
2302
|
}
|
|
2303
|
+
lines.push("---");
|
|
2304
|
+
lines.push("Call this tool again with **confirmed=true** to save.");
|
|
2305
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2405
2306
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
params
|
|
2413
|
-
);
|
|
2414
|
-
if (!newsletters || newsletters.length === 0) {
|
|
2415
|
-
return {
|
|
2416
|
-
content: [
|
|
2417
|
-
{
|
|
2418
|
-
type: "text",
|
|
2419
|
-
text: "No past newsletters found. This will be the first edition!"
|
|
2420
|
-
}
|
|
2421
|
-
]
|
|
2422
|
-
};
|
|
2423
|
-
}
|
|
2424
|
-
const lines = [];
|
|
2425
|
-
lines.push(
|
|
2426
|
-
`## Past Newsletters (${newsletters.length} most recent)`
|
|
2427
|
-
);
|
|
2428
|
-
lines.push("");
|
|
2429
|
-
for (const nl of newsletters) {
|
|
2430
|
-
lines.push(`### ${nl.subject} -- ${nl.sentAt || nl.createdAt}`);
|
|
2431
|
-
if (nl.notes) lines.push(`Notes: ${nl.notes}`);
|
|
2432
|
-
if (nl.metrics) {
|
|
2433
|
-
const m = nl.metrics;
|
|
2434
|
-
const parts = [];
|
|
2435
|
-
if (m.open_rate) parts.push(`Open rate: ${m.open_rate}`);
|
|
2436
|
-
if (m.click_rate) parts.push(`Click rate: ${m.click_rate}`);
|
|
2437
|
-
if (parts.length) lines.push(`Metrics: ${parts.join(", ")}`);
|
|
2307
|
+
const result = await client.updateAudience(String(target.id), payload);
|
|
2308
|
+
return {
|
|
2309
|
+
content: [
|
|
2310
|
+
{
|
|
2311
|
+
type: "text",
|
|
2312
|
+
text: `Audience "${result.name}" updated successfully.`
|
|
2438
2313
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
lines.push(textContent);
|
|
2442
|
-
lines.push(
|
|
2443
|
-
`[Full content available by calling get_past_newsletters with newsletterId: ${nl.id}]`
|
|
2444
|
-
);
|
|
2445
|
-
lines.push("");
|
|
2446
|
-
}
|
|
2447
|
-
const mostRecent = newsletters[0];
|
|
2448
|
-
return {
|
|
2449
|
-
content: [
|
|
2450
|
-
{ type: "text", text: lines.join("\n") },
|
|
2451
|
-
...mostRecent?.contentHtml ? [{ type: "text", text: `NEWSLETTER_HTML_PREVIEW:
|
|
2452
|
-
${mostRecent.contentHtml}` }] : []
|
|
2453
|
-
]
|
|
2454
|
-
};
|
|
2455
|
-
} catch (error) {
|
|
2456
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2457
|
-
throw new Error(`Failed to fetch past newsletters: ${message}`);
|
|
2458
|
-
}
|
|
2314
|
+
]
|
|
2315
|
+
};
|
|
2459
2316
|
}
|
|
2460
2317
|
);
|
|
2461
2318
|
server.tool(
|
|
2462
|
-
"
|
|
2463
|
-
"
|
|
2319
|
+
"delete_audience",
|
|
2320
|
+
"Delete an audience profile by name. Set confirmed=false to preview first.",
|
|
2464
2321
|
{
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
templateId: z11.string().optional().describe("The template ID used to generate this newsletter."),
|
|
2468
|
-
notes: z11.string().optional().describe(
|
|
2469
|
-
"Optional notes about this newsletter (what worked, theme, etc)."
|
|
2470
|
-
)
|
|
2322
|
+
name: z9.string().describe("Name of the audience to delete"),
|
|
2323
|
+
confirmed: z9.boolean().default(false).describe("Set to true to confirm deletion. If false, returns a confirmation prompt.")
|
|
2471
2324
|
},
|
|
2472
2325
|
{
|
|
2473
|
-
title: "
|
|
2326
|
+
title: "Delete Audience",
|
|
2474
2327
|
readOnlyHint: false,
|
|
2475
|
-
destructiveHint:
|
|
2328
|
+
destructiveHint: true,
|
|
2476
2329
|
idempotentHint: false,
|
|
2477
2330
|
openWorldHint: false
|
|
2478
2331
|
},
|
|
2479
|
-
async (
|
|
2332
|
+
async (args) => {
|
|
2333
|
+
let target;
|
|
2480
2334
|
try {
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
contentHtml
|
|
2484
|
-
};
|
|
2485
|
-
if (templateId) data.templateId = Number(templateId);
|
|
2486
|
-
if (notes) data.notes = notes;
|
|
2487
|
-
await client.saveNewsletterToArchive(data);
|
|
2335
|
+
target = await client.getAudienceByName(args.name);
|
|
2336
|
+
} catch {
|
|
2488
2337
|
return {
|
|
2489
2338
|
content: [
|
|
2490
2339
|
{
|
|
2491
2340
|
type: "text",
|
|
2492
|
-
text: `
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
]
|
|
2341
|
+
text: `Audience "${args.name}" not found. Use list_audiences to see available profiles.`
|
|
2342
|
+
}
|
|
2343
|
+
],
|
|
2344
|
+
isError: true
|
|
2497
2345
|
};
|
|
2498
|
-
} catch (error) {
|
|
2499
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2500
|
-
throw new Error(`Failed to save newsletter: ${message}`);
|
|
2501
2346
|
}
|
|
2502
|
-
|
|
2503
|
-
);
|
|
2504
|
-
server.tool(
|
|
2505
|
-
"save_newsletter_template",
|
|
2506
|
-
"Create or update a newsletter template with HTML and section definitions.",
|
|
2507
|
-
{
|
|
2508
|
-
template_id: z11.string().optional().describe(
|
|
2509
|
-
"If provided, updates this existing template. Otherwise creates a new one."
|
|
2510
|
-
),
|
|
2511
|
-
name: z11.string().min(1).max(255).describe("Template name"),
|
|
2512
|
-
description: z11.string().max(2e3).optional().describe("What this template is for"),
|
|
2513
|
-
html_content: z11.string().min(1).max(1e5).describe(
|
|
2514
|
-
"Full HTML template with {{placeholder}} variables."
|
|
2515
|
-
),
|
|
2516
|
-
sections: z11.array(
|
|
2517
|
-
z11.object({
|
|
2518
|
-
name: z11.string().describe("Section identifier, e.g. 'intro'"),
|
|
2519
|
-
label: z11.string().describe("Display label, e.g. 'Intro/Greeting'"),
|
|
2520
|
-
required: z11.boolean().optional().describe("Whether this section is required"),
|
|
2521
|
-
item_count: z11.number().optional().describe("Number of items in this section, if applicable")
|
|
2522
|
-
})
|
|
2523
|
-
).max(20).optional().describe("Array of section metadata describing the template structure"),
|
|
2524
|
-
style_config: z11.object({
|
|
2525
|
-
primary_color: z11.string().optional(),
|
|
2526
|
-
accent_color: z11.string().optional(),
|
|
2527
|
-
link_color: z11.string().optional(),
|
|
2528
|
-
background_color: z11.string().optional(),
|
|
2529
|
-
font_family: z11.string().optional(),
|
|
2530
|
-
max_width: z11.string().optional()
|
|
2531
|
-
}).optional().describe("Colors, fonts, and brand configuration"),
|
|
2532
|
-
is_default: z11.boolean().optional().describe(
|
|
2533
|
-
"Set as the default template for this customer."
|
|
2534
|
-
)
|
|
2535
|
-
},
|
|
2536
|
-
{
|
|
2537
|
-
title: "Save Newsletter Template",
|
|
2538
|
-
readOnlyHint: false,
|
|
2539
|
-
destructiveHint: false,
|
|
2540
|
-
idempotentHint: false,
|
|
2541
|
-
openWorldHint: false
|
|
2542
|
-
},
|
|
2543
|
-
async ({
|
|
2544
|
-
template_id,
|
|
2545
|
-
name,
|
|
2546
|
-
description,
|
|
2547
|
-
html_content,
|
|
2548
|
-
sections,
|
|
2549
|
-
style_config,
|
|
2550
|
-
is_default
|
|
2551
|
-
}) => {
|
|
2552
|
-
try {
|
|
2553
|
-
const data = {
|
|
2554
|
-
name,
|
|
2555
|
-
htmlContent: html_content
|
|
2556
|
-
};
|
|
2557
|
-
if (description !== void 0) data.description = description;
|
|
2558
|
-
if (sections !== void 0) {
|
|
2559
|
-
data.sections = sections.map((s) => ({
|
|
2560
|
-
id: s.name,
|
|
2561
|
-
type: "custom",
|
|
2562
|
-
label: s.label,
|
|
2563
|
-
required: s.required ?? true,
|
|
2564
|
-
count: s.item_count
|
|
2565
|
-
}));
|
|
2566
|
-
}
|
|
2567
|
-
if (style_config !== void 0) {
|
|
2568
|
-
data.style = {
|
|
2569
|
-
primary_color: style_config.primary_color,
|
|
2570
|
-
accent_color: style_config.accent_color,
|
|
2571
|
-
link_color: style_config.link_color,
|
|
2572
|
-
background_color: style_config.background_color,
|
|
2573
|
-
font_family: style_config.font_family,
|
|
2574
|
-
content_width: style_config.max_width
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
if (is_default !== void 0) data.isDefault = is_default;
|
|
2578
|
-
let result;
|
|
2579
|
-
if (template_id) {
|
|
2580
|
-
result = await client.updateTemplate(
|
|
2581
|
-
template_id,
|
|
2582
|
-
data
|
|
2583
|
-
);
|
|
2584
|
-
} else {
|
|
2585
|
-
result = await client.createTemplate(data);
|
|
2586
|
-
}
|
|
2587
|
-
const action = template_id ? "updated" : "created";
|
|
2347
|
+
if (args.confirmed !== true) {
|
|
2588
2348
|
return {
|
|
2589
2349
|
content: [
|
|
2590
2350
|
{
|
|
2591
2351
|
type: "text",
|
|
2592
|
-
text:
|
|
2352
|
+
text: `## Delete Audience Confirmation
|
|
2353
|
+
|
|
2354
|
+
**Name:** ${target.name}
|
|
2355
|
+
${target.description ? `**Description:** ${target.description}
|
|
2356
|
+
` : ""}
|
|
2357
|
+
This action cannot be undone.
|
|
2358
|
+
|
|
2359
|
+
Call this tool again with confirmed=true to delete.`
|
|
2593
2360
|
}
|
|
2594
2361
|
]
|
|
2595
2362
|
};
|
|
2596
|
-
} catch (error) {
|
|
2597
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2598
|
-
throw new Error(`Failed to save newsletter template: ${message}`);
|
|
2599
2363
|
}
|
|
2364
|
+
await client.deleteAudience(String(target.id));
|
|
2365
|
+
return {
|
|
2366
|
+
content: [
|
|
2367
|
+
{
|
|
2368
|
+
type: "text",
|
|
2369
|
+
text: `Audience "${target.name}" has been deleted.`
|
|
2370
|
+
}
|
|
2371
|
+
]
|
|
2372
|
+
};
|
|
2600
2373
|
}
|
|
2601
2374
|
);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// src/tools/newsletter-template.ts
|
|
2378
|
+
function registerNewsletterTemplateTools(server, client) {
|
|
2602
2379
|
server.tool(
|
|
2603
|
-
"
|
|
2604
|
-
"List
|
|
2380
|
+
"list_esp_templates",
|
|
2381
|
+
"List available email templates from the connected ESP. Use the returned template IDs with create_newsletter's template_id parameter.",
|
|
2605
2382
|
{},
|
|
2606
2383
|
{
|
|
2607
|
-
title: "List
|
|
2384
|
+
title: "List ESP Templates",
|
|
2608
2385
|
readOnlyHint: true,
|
|
2609
2386
|
destructiveHint: false,
|
|
2610
2387
|
idempotentHint: true,
|
|
2611
|
-
openWorldHint:
|
|
2388
|
+
openWorldHint: true
|
|
2612
2389
|
},
|
|
2613
2390
|
async () => {
|
|
2614
2391
|
try {
|
|
2615
|
-
const
|
|
2616
|
-
|
|
2392
|
+
const result = await client.listEmailTemplates();
|
|
2393
|
+
const templates = result?.templates ?? [];
|
|
2394
|
+
if (templates.length === 0) {
|
|
2617
2395
|
return {
|
|
2618
2396
|
content: [
|
|
2619
2397
|
{
|
|
2620
2398
|
type: "text",
|
|
2621
|
-
text: "No
|
|
2399
|
+
text: "No email templates found in your ESP. Create templates in your ESP dashboard (Kit, Beehiiv, or Mailchimp) and they will appear here."
|
|
2622
2400
|
}
|
|
2623
2401
|
]
|
|
2624
2402
|
};
|
|
2625
2403
|
}
|
|
2626
2404
|
const lines = [];
|
|
2627
|
-
lines.push(`##
|
|
2405
|
+
lines.push(`## ESP Email Templates (${templates.length})`);
|
|
2406
|
+
lines.push("");
|
|
2407
|
+
lines.push("Pass a template ID to `create_newsletter` via the `template_id` parameter to use it.");
|
|
2628
2408
|
lines.push("");
|
|
2629
2409
|
for (const t of templates) {
|
|
2630
|
-
|
|
2631
|
-
if (t.isDefault) badges.push("DEFAULT");
|
|
2632
|
-
if (t.htmlContent) badges.push("HAS HTML");
|
|
2633
|
-
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
2634
|
-
lines.push(`**${t.name}** (ID: ${t.id})${badgeStr}`);
|
|
2635
|
-
if (t.description) lines.push(` ${t.description}`);
|
|
2636
|
-
if (t.sections && t.sections.length > 0) {
|
|
2637
|
-
lines.push(
|
|
2638
|
-
` Sections: ${t.sections.map((s) => s.label).join(", ")}`
|
|
2639
|
-
);
|
|
2640
|
-
}
|
|
2641
|
-
lines.push("");
|
|
2410
|
+
lines.push(`- **${t.name}** (ID: ${t.id})`);
|
|
2642
2411
|
}
|
|
2643
2412
|
return {
|
|
2644
2413
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
2645
2414
|
};
|
|
2646
2415
|
} catch (error) {
|
|
2647
2416
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2648
|
-
throw new Error(`Failed to list
|
|
2649
|
-
}
|
|
2650
|
-
}
|
|
2651
|
-
);
|
|
2652
|
-
server.tool(
|
|
2653
|
-
"delete_newsletter_template",
|
|
2654
|
-
"Delete a newsletter template. Set confirmed=true to proceed.",
|
|
2655
|
-
{
|
|
2656
|
-
template_id: z11.string().describe("The ID of the newsletter template to delete."),
|
|
2657
|
-
confirmed: z11.boolean().default(false).describe(
|
|
2658
|
-
"Must be true to actually delete. If false, returns a confirmation prompt."
|
|
2659
|
-
)
|
|
2660
|
-
},
|
|
2661
|
-
{
|
|
2662
|
-
title: "Delete Newsletter Template",
|
|
2663
|
-
readOnlyHint: false,
|
|
2664
|
-
destructiveHint: true,
|
|
2665
|
-
idempotentHint: false,
|
|
2666
|
-
openWorldHint: false
|
|
2667
|
-
},
|
|
2668
|
-
async ({ template_id, confirmed }) => {
|
|
2669
|
-
try {
|
|
2670
|
-
if (!confirmed) {
|
|
2671
|
-
const template = await client.getTemplate(template_id);
|
|
2672
|
-
return {
|
|
2673
|
-
content: [
|
|
2674
|
-
{
|
|
2675
|
-
type: "text",
|
|
2676
|
-
text: `Are you sure you want to delete the template "${template.name}" (ID: ${template.id})? This action cannot be undone. Call delete_newsletter_template again with confirmed=true to proceed.`
|
|
2677
|
-
}
|
|
2678
|
-
]
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
|
-
await client.deleteTemplate(template_id);
|
|
2682
|
-
return {
|
|
2683
|
-
content: [
|
|
2684
|
-
{
|
|
2685
|
-
type: "text",
|
|
2686
|
-
text: `Newsletter template (ID: ${template_id}) has been deleted.`
|
|
2687
|
-
}
|
|
2688
|
-
]
|
|
2689
|
-
};
|
|
2690
|
-
} catch (error) {
|
|
2691
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2692
|
-
if (message.includes("404")) {
|
|
2693
|
-
return {
|
|
2694
|
-
content: [
|
|
2695
|
-
{
|
|
2696
|
-
type: "text",
|
|
2697
|
-
text: "Template not found. It may have already been deleted."
|
|
2698
|
-
}
|
|
2699
|
-
]
|
|
2700
|
-
};
|
|
2701
|
-
}
|
|
2702
|
-
throw new Error(`Failed to delete newsletter template: ${message}`);
|
|
2417
|
+
throw new Error(`Failed to list ESP templates: ${message}`);
|
|
2703
2418
|
}
|
|
2704
2419
|
}
|
|
2705
2420
|
);
|
|
2706
2421
|
}
|
|
2707
2422
|
|
|
2708
2423
|
// src/tools/calendar.ts
|
|
2709
|
-
import { z as
|
|
2424
|
+
import { z as z10 } from "zod";
|
|
2710
2425
|
function registerCalendarTools(server, client) {
|
|
2711
2426
|
server.tool(
|
|
2712
2427
|
"get_calendar",
|
|
2713
2428
|
"View the content calendar with all scheduled, published, and draft posts.",
|
|
2714
2429
|
{
|
|
2715
|
-
from:
|
|
2716
|
-
to:
|
|
2717
|
-
status:
|
|
2718
|
-
type:
|
|
2430
|
+
from: z10.string().optional().describe("ISO date to filter from, e.g. 2024-01-01"),
|
|
2431
|
+
to: z10.string().optional().describe("ISO date to filter to, e.g. 2024-01-31"),
|
|
2432
|
+
status: z10.string().optional().describe("Filter by status: scheduled, published, draft, failed"),
|
|
2433
|
+
type: z10.string().optional().describe("Filter by type: social_post or newsletter")
|
|
2719
2434
|
},
|
|
2720
2435
|
{
|
|
2721
2436
|
title: "Get Content Calendar",
|
|
@@ -2851,10 +2566,10 @@ Next slot: ${slotDate.toLocaleString()} (${dayNames[slotDate.getDay()]})
|
|
|
2851
2566
|
"schedule_to_queue",
|
|
2852
2567
|
"Add a post to the next available queue slot. Check get_queue first. Set confirmed=false to preview first.",
|
|
2853
2568
|
{
|
|
2854
|
-
content:
|
|
2855
|
-
platforms:
|
|
2856
|
-
media_urls:
|
|
2857
|
-
confirmed:
|
|
2569
|
+
content: z10.string().describe("The text content of the post"),
|
|
2570
|
+
platforms: z10.array(z10.string()).describe('Platforms to post to, e.g. ["twitter", "linkedin"]'),
|
|
2571
|
+
media_urls: z10.array(z10.string()).optional().describe("Public URLs of media to attach"),
|
|
2572
|
+
confirmed: z10.boolean().default(false).describe(
|
|
2858
2573
|
"Set to true to confirm scheduling. If false or missing, returns a preview for user approval."
|
|
2859
2574
|
)
|
|
2860
2575
|
},
|
|
@@ -2936,7 +2651,7 @@ ${JSON.stringify(result, null, 2)}`
|
|
|
2936
2651
|
}
|
|
2937
2652
|
|
|
2938
2653
|
// src/tools/notifications.ts
|
|
2939
|
-
import { z as
|
|
2654
|
+
import { z as z11 } from "zod";
|
|
2940
2655
|
function timeAgo(dateStr) {
|
|
2941
2656
|
const diff = Date.now() - new Date(dateStr).getTime();
|
|
2942
2657
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -2952,8 +2667,8 @@ function registerNotificationTools(server, client) {
|
|
|
2952
2667
|
"get_notifications",
|
|
2953
2668
|
"Get recent notifications including post results, account warnings, and errors.",
|
|
2954
2669
|
{
|
|
2955
|
-
unread_only:
|
|
2956
|
-
limit:
|
|
2670
|
+
unread_only: z11.boolean().optional().describe("If true, only show unread notifications. Default false."),
|
|
2671
|
+
limit: z11.number().optional().describe("Max notifications to return. Default 10.")
|
|
2957
2672
|
},
|
|
2958
2673
|
{
|
|
2959
2674
|
title: "Get Notifications",
|
|
@@ -3019,71 +2734,8 @@ Showing ${notifications.length} of ${result.unread_count !== void 0 ? "total" :
|
|
|
3019
2734
|
);
|
|
3020
2735
|
}
|
|
3021
2736
|
|
|
3022
|
-
// src/tools/publishing-rules.ts
|
|
3023
|
-
var WORKFLOW = {
|
|
3024
|
-
before_writing_content: [
|
|
3025
|
-
"Call get_brand_voice to load tone and style rules",
|
|
3026
|
-
"Call get_audience to understand who the content is for",
|
|
3027
|
-
"For newsletters: also call get_newsletter_template and get_past_newsletters"
|
|
3028
|
-
],
|
|
3029
|
-
before_publishing: [
|
|
3030
|
-
"Always set confirmed=false first to generate a preview",
|
|
3031
|
-
"Show the user a visual preview before confirming",
|
|
3032
|
-
"Never set confirmed=true without explicit user approval",
|
|
3033
|
-
"For newsletters: show subscriber count and send time before confirming",
|
|
3034
|
-
"If require_double_confirm is true, ask for confirmation twice"
|
|
3035
|
-
],
|
|
3036
|
-
replies_and_engagement: [
|
|
3037
|
-
"Draft the reply and show it before sending",
|
|
3038
|
-
"Set confirmed=false first to preview",
|
|
3039
|
-
"Remind user that replies are public"
|
|
3040
|
-
],
|
|
3041
|
-
newsletters: [
|
|
3042
|
-
"Always render newsletter as HTML artifact/preview before pushing to ESP",
|
|
3043
|
-
"Use table-based layouts, inline CSS only, 600px max width",
|
|
3044
|
-
"Email-safe fonts only: Arial, Helvetica, Georgia, Verdana",
|
|
3045
|
-
"All images must use absolute URLs",
|
|
3046
|
-
"Keep total email under 102KB to avoid Gmail clipping"
|
|
3047
|
-
]
|
|
3048
|
-
};
|
|
3049
|
-
function registerPublishingRulesTools(server, client) {
|
|
3050
|
-
server.tool(
|
|
3051
|
-
"get_publishing_rules",
|
|
3052
|
-
"Get publishing rules and the content creation workflow. Call this BEFORE publishing, scheduling, or sending any content.",
|
|
3053
|
-
{},
|
|
3054
|
-
{
|
|
3055
|
-
title: "Get Publishing Rules",
|
|
3056
|
-
readOnlyHint: true,
|
|
3057
|
-
destructiveHint: false,
|
|
3058
|
-
idempotentHint: true,
|
|
3059
|
-
openWorldHint: false
|
|
3060
|
-
},
|
|
3061
|
-
async () => {
|
|
3062
|
-
const rules = await client.getPublishingRules();
|
|
3063
|
-
const response = {
|
|
3064
|
-
rules: {
|
|
3065
|
-
social_default_action: rules.socialDefaultAction ?? "draft",
|
|
3066
|
-
newsletter_default_action: rules.newsletterDefaultAction ?? "draft",
|
|
3067
|
-
require_preview_before_publish: rules.requirePreviewBeforePublish ?? true,
|
|
3068
|
-
require_double_confirm_newsletter: rules.requireDoubleConfirmNewsletter ?? true,
|
|
3069
|
-
require_double_confirm_social: rules.requireDoubleConfirmSocial ?? false,
|
|
3070
|
-
allow_immediate_publish: rules.allowImmediatePublish ?? false,
|
|
3071
|
-
allow_immediate_send: rules.allowImmediateSend ?? false,
|
|
3072
|
-
max_posts_per_day: rules.maxPostsPerDay ?? null,
|
|
3073
|
-
blocked_words: rules.blockedWords ?? [],
|
|
3074
|
-
required_disclaimer: rules.requiredDisclaimer ?? null
|
|
3075
|
-
},
|
|
3076
|
-
workflow: WORKFLOW
|
|
3077
|
-
};
|
|
3078
|
-
return {
|
|
3079
|
-
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
3080
|
-
};
|
|
3081
|
-
}
|
|
3082
|
-
);
|
|
3083
|
-
}
|
|
3084
|
-
|
|
3085
2737
|
// src/tools/sources.ts
|
|
3086
|
-
import { z as
|
|
2738
|
+
import { z as z12 } from "zod";
|
|
3087
2739
|
var TYPE_LABELS = {
|
|
3088
2740
|
feed: "RSS Feed",
|
|
3089
2741
|
website: "Website",
|
|
@@ -3103,14 +2755,15 @@ var TYPE_HINTS = {
|
|
|
3103
2755
|
social: "Use web_search to check recent activity"
|
|
3104
2756
|
};
|
|
3105
2757
|
function registerSourceTools(server, client) {
|
|
2758
|
+
const MAX_SOURCES = 10;
|
|
3106
2759
|
server.tool(
|
|
3107
2760
|
"get_sources",
|
|
3108
|
-
"
|
|
2761
|
+
"View your saved content sources (maximum 10). Shows RSS feeds, websites, and channels you're monitoring.",
|
|
3109
2762
|
{
|
|
3110
|
-
type:
|
|
2763
|
+
type: z12.string().optional().describe(
|
|
3111
2764
|
"Optional: filter by type (feed, website, youtube, search, podcast, reddit, social)"
|
|
3112
2765
|
),
|
|
3113
|
-
category:
|
|
2766
|
+
category: z12.string().optional().describe("Optional: filter by category")
|
|
3114
2767
|
},
|
|
3115
2768
|
{
|
|
3116
2769
|
title: "Get Content Sources",
|
|
@@ -3130,12 +2783,12 @@ function registerSourceTools(server, client) {
|
|
|
3130
2783
|
content: [
|
|
3131
2784
|
{
|
|
3132
2785
|
type: "text",
|
|
3133
|
-
text:
|
|
2786
|
+
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.`
|
|
3134
2787
|
}
|
|
3135
2788
|
]
|
|
3136
2789
|
};
|
|
3137
2790
|
}
|
|
3138
|
-
const lines = [`## Content Sources (${sources.length})
|
|
2791
|
+
const lines = [`## Content Sources (${sources.length} of ${MAX_SOURCES})
|
|
3139
2792
|
`];
|
|
3140
2793
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3141
2794
|
for (const s of sources) {
|
|
@@ -3171,13 +2824,13 @@ function registerSourceTools(server, client) {
|
|
|
3171
2824
|
);
|
|
3172
2825
|
server.tool(
|
|
3173
2826
|
"add_source",
|
|
3174
|
-
"Save a new content source to monitor.",
|
|
2827
|
+
"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.",
|
|
3175
2828
|
{
|
|
3176
|
-
name:
|
|
3177
|
-
url:
|
|
2829
|
+
name: z12.string().describe("Display name for the source"),
|
|
2830
|
+
url: z12.string().optional().describe(
|
|
3178
2831
|
"URL of the source. Not needed for 'search' type."
|
|
3179
2832
|
),
|
|
3180
|
-
type:
|
|
2833
|
+
type: z12.enum([
|
|
3181
2834
|
"feed",
|
|
3182
2835
|
"website",
|
|
3183
2836
|
"youtube",
|
|
@@ -3186,12 +2839,12 @@ function registerSourceTools(server, client) {
|
|
|
3186
2839
|
"reddit",
|
|
3187
2840
|
"social"
|
|
3188
2841
|
]).describe("Type of source. Determines how to fetch content from it."),
|
|
3189
|
-
category:
|
|
2842
|
+
category: z12.string().optional().describe(
|
|
3190
2843
|
"Optional category for organization (e.g. 'competitors', 'industry', 'inspiration')"
|
|
3191
2844
|
),
|
|
3192
|
-
tags:
|
|
3193
|
-
notes:
|
|
3194
|
-
searchQuery:
|
|
2845
|
+
tags: z12.array(z12.string()).optional().describe("Optional tags for filtering"),
|
|
2846
|
+
notes: z12.string().optional().describe("Optional notes about why this source matters"),
|
|
2847
|
+
searchQuery: z12.string().optional().describe(
|
|
3195
2848
|
"For 'search' type only: the keyword or topic to search for"
|
|
3196
2849
|
)
|
|
3197
2850
|
},
|
|
@@ -3203,8 +2856,20 @@ function registerSourceTools(server, client) {
|
|
|
3203
2856
|
openWorldHint: false
|
|
3204
2857
|
},
|
|
3205
2858
|
async (args) => {
|
|
3206
|
-
const
|
|
3207
|
-
|
|
2859
|
+
const existing = await client.listSources({});
|
|
2860
|
+
if (existing.sources.length >= MAX_SOURCES) {
|
|
2861
|
+
return {
|
|
2862
|
+
content: [
|
|
2863
|
+
{
|
|
2864
|
+
type: "text",
|
|
2865
|
+
text: `You've reached the maximum of ${MAX_SOURCES} sources. Remove an existing source before adding a new one.`
|
|
2866
|
+
}
|
|
2867
|
+
],
|
|
2868
|
+
isError: true
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
const data = {
|
|
2872
|
+
name: args.name,
|
|
3208
2873
|
type: args.type
|
|
3209
2874
|
};
|
|
3210
2875
|
if (args.url) data.url = args.url;
|
|
@@ -3214,6 +2879,7 @@ function registerSourceTools(server, client) {
|
|
|
3214
2879
|
if (args.searchQuery) data.searchQuery = args.searchQuery;
|
|
3215
2880
|
const result = await client.createSource(data);
|
|
3216
2881
|
const typeLabel = TYPE_LABELS[result.type] || result.type;
|
|
2882
|
+
const remaining = MAX_SOURCES - existing.sources.length - 1;
|
|
3217
2883
|
const lines = [
|
|
3218
2884
|
`## Source Added
|
|
3219
2885
|
`,
|
|
@@ -3224,6 +2890,8 @@ function registerSourceTools(server, client) {
|
|
|
3224
2890
|
if (result.searchQuery)
|
|
3225
2891
|
lines.push(`- Search: "${result.searchQuery}"`);
|
|
3226
2892
|
if (result.category) lines.push(`- Category: ${result.category}`);
|
|
2893
|
+
lines.push(`
|
|
2894
|
+
*${existing.sources.length + 1} of ${MAX_SOURCES} sources used (${remaining} remaining)*`);
|
|
3227
2895
|
return {
|
|
3228
2896
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
3229
2897
|
};
|
|
@@ -3233,10 +2901,10 @@ function registerSourceTools(server, client) {
|
|
|
3233
2901
|
"update_source",
|
|
3234
2902
|
"Update a saved content source.",
|
|
3235
2903
|
{
|
|
3236
|
-
source_id:
|
|
3237
|
-
name:
|
|
3238
|
-
url:
|
|
3239
|
-
type:
|
|
2904
|
+
source_id: z12.number().describe("The ID of the source to update"),
|
|
2905
|
+
name: z12.string().optional().describe("New display name"),
|
|
2906
|
+
url: z12.string().optional().describe("New URL"),
|
|
2907
|
+
type: z12.enum([
|
|
3240
2908
|
"feed",
|
|
3241
2909
|
"website",
|
|
3242
2910
|
"youtube",
|
|
@@ -3245,11 +2913,11 @@ function registerSourceTools(server, client) {
|
|
|
3245
2913
|
"reddit",
|
|
3246
2914
|
"social"
|
|
3247
2915
|
]).optional().describe("New source type"),
|
|
3248
|
-
category:
|
|
3249
|
-
tags:
|
|
3250
|
-
notes:
|
|
3251
|
-
searchQuery:
|
|
3252
|
-
isActive:
|
|
2916
|
+
category: z12.string().optional().describe("New category"),
|
|
2917
|
+
tags: z12.array(z12.string()).optional().describe("New tags"),
|
|
2918
|
+
notes: z12.string().optional().describe("New notes"),
|
|
2919
|
+
searchQuery: z12.string().optional().describe("New search query"),
|
|
2920
|
+
isActive: z12.boolean().optional().describe("Set to false to deactivate")
|
|
3253
2921
|
},
|
|
3254
2922
|
{
|
|
3255
2923
|
title: "Update Content Source",
|
|
@@ -3279,8 +2947,8 @@ function registerSourceTools(server, client) {
|
|
|
3279
2947
|
"remove_source",
|
|
3280
2948
|
"Remove a content source. Set confirmed=false to preview first.",
|
|
3281
2949
|
{
|
|
3282
|
-
source_id:
|
|
3283
|
-
confirmed:
|
|
2950
|
+
source_id: z12.number().describe("The ID of the source to remove"),
|
|
2951
|
+
confirmed: z12.boolean().default(false).describe("Set to true to confirm deletion. If false or missing, returns a preview for user approval.")
|
|
3284
2952
|
},
|
|
3285
2953
|
{
|
|
3286
2954
|
title: "Remove Content Source",
|
|
@@ -3310,6 +2978,823 @@ This will permanently remove this content source. Call this tool again with conf
|
|
|
3310
2978
|
}
|
|
3311
2979
|
);
|
|
3312
2980
|
}
|
|
2981
|
+
|
|
2982
|
+
// src/tools/wordpress.ts
|
|
2983
|
+
import { z as z13 } from "zod";
|
|
2984
|
+
function registerWordPressTools(server, client) {
|
|
2985
|
+
server.tool(
|
|
2986
|
+
"wp_create_post",
|
|
2987
|
+
"Create a new WordPress post. Defaults to draft status. Set confirmed=false to preview before publishing.",
|
|
2988
|
+
{
|
|
2989
|
+
title: z13.string().optional().describe("Post title"),
|
|
2990
|
+
content: z13.string().optional().describe("Post content (HTML)"),
|
|
2991
|
+
status: z13.enum(["draft", "publish", "pending", "private", "future"]).default("draft").describe("Post status. Use 'future' with date for scheduling."),
|
|
2992
|
+
categories: z13.array(z13.number()).optional().describe("Array of category IDs"),
|
|
2993
|
+
tags: z13.array(z13.number()).optional().describe("Array of tag IDs"),
|
|
2994
|
+
featured_media: z13.number().optional().describe("Featured image attachment ID"),
|
|
2995
|
+
excerpt: z13.string().optional().describe("Post excerpt"),
|
|
2996
|
+
date: z13.string().optional().describe(
|
|
2997
|
+
"Publication date in ISO 8601 format. Required when status is 'future'."
|
|
2998
|
+
),
|
|
2999
|
+
slug: z13.string().optional().describe("URL slug for the post"),
|
|
3000
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3001
|
+
"Set to true to confirm creation. If false, returns a preview for approval. Required when status is 'publish'."
|
|
3002
|
+
)
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
title: "Create WordPress Post",
|
|
3006
|
+
readOnlyHint: false,
|
|
3007
|
+
destructiveHint: false,
|
|
3008
|
+
idempotentHint: false,
|
|
3009
|
+
openWorldHint: true
|
|
3010
|
+
},
|
|
3011
|
+
async (args) => {
|
|
3012
|
+
if (args.confirmed !== true && (args.status === "publish" || args.status === "future")) {
|
|
3013
|
+
const preview = `## WordPress Post Preview
|
|
3014
|
+
|
|
3015
|
+
**Title:** ${args.title ?? "(no title)"}
|
|
3016
|
+
**Status:** ${args.status}
|
|
3017
|
+
**Content length:** ${(args.content ?? "").length} characters
|
|
3018
|
+
` + (args.categories?.length ? `**Categories:** ${args.categories.join(", ")}
|
|
3019
|
+
` : "") + (args.tags?.length ? `**Tags:** ${args.tags.join(", ")}
|
|
3020
|
+
` : "") + (args.date ? `**Date:** ${args.date}
|
|
3021
|
+
` : "") + (args.slug ? `**Slug:** ${args.slug}
|
|
3022
|
+
` : "") + (args.excerpt ? `**Excerpt:** ${args.excerpt.slice(0, 200)}
|
|
3023
|
+
` : "") + `
|
|
3024
|
+
**Action:** This will ${args.status === "publish" ? "publish immediately" : "schedule"} on your WordPress site.
|
|
3025
|
+
|
|
3026
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3027
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3028
|
+
}
|
|
3029
|
+
const body = {
|
|
3030
|
+
title: args.title,
|
|
3031
|
+
content: args.content,
|
|
3032
|
+
status: args.status
|
|
3033
|
+
};
|
|
3034
|
+
if (args.categories) body.categories = args.categories;
|
|
3035
|
+
if (args.tags) body.tags = args.tags;
|
|
3036
|
+
if (args.featured_media) body.featured_media = args.featured_media;
|
|
3037
|
+
if (args.excerpt) body.excerpt = args.excerpt;
|
|
3038
|
+
if (args.date) body.date = args.date;
|
|
3039
|
+
if (args.slug) body.slug = args.slug;
|
|
3040
|
+
const result = await client.wpCreatePost(body);
|
|
3041
|
+
return {
|
|
3042
|
+
content: [
|
|
3043
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3044
|
+
]
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
);
|
|
3048
|
+
server.tool(
|
|
3049
|
+
"wp_update_post",
|
|
3050
|
+
"Update an existing WordPress post by ID.",
|
|
3051
|
+
{
|
|
3052
|
+
post_id: z13.number().describe("The WordPress post ID to update"),
|
|
3053
|
+
title: z13.string().optional().describe("New post title"),
|
|
3054
|
+
content: z13.string().optional().describe("New post content (HTML)"),
|
|
3055
|
+
status: z13.enum(["draft", "publish", "pending", "private", "future"]).optional().describe("New post status"),
|
|
3056
|
+
categories: z13.array(z13.number()).optional().describe("New category IDs"),
|
|
3057
|
+
tags: z13.array(z13.number()).optional().describe("New tag IDs"),
|
|
3058
|
+
featured_media: z13.number().optional().describe("New featured image attachment ID"),
|
|
3059
|
+
excerpt: z13.string().optional().describe("New post excerpt"),
|
|
3060
|
+
date: z13.string().optional().describe("New publication date (ISO 8601)"),
|
|
3061
|
+
slug: z13.string().optional().describe("New URL slug")
|
|
3062
|
+
},
|
|
3063
|
+
{
|
|
3064
|
+
title: "Update WordPress Post",
|
|
3065
|
+
readOnlyHint: false,
|
|
3066
|
+
destructiveHint: false,
|
|
3067
|
+
idempotentHint: true,
|
|
3068
|
+
openWorldHint: true
|
|
3069
|
+
},
|
|
3070
|
+
async (args) => {
|
|
3071
|
+
const { post_id, ...data } = args;
|
|
3072
|
+
const result = await client.wpUpdatePost(post_id, data);
|
|
3073
|
+
return {
|
|
3074
|
+
content: [
|
|
3075
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3076
|
+
]
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
);
|
|
3080
|
+
server.tool(
|
|
3081
|
+
"wp_publish_post",
|
|
3082
|
+
"Change a WordPress post's status to 'publish'. Requires confirmation.",
|
|
3083
|
+
{
|
|
3084
|
+
post_id: z13.number().describe("The WordPress post ID to publish"),
|
|
3085
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3086
|
+
"Set to true to confirm publishing. If false, returns a preview."
|
|
3087
|
+
)
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
title: "Publish WordPress Post",
|
|
3091
|
+
readOnlyHint: false,
|
|
3092
|
+
destructiveHint: false,
|
|
3093
|
+
idempotentHint: true,
|
|
3094
|
+
openWorldHint: true
|
|
3095
|
+
},
|
|
3096
|
+
async (args) => {
|
|
3097
|
+
if (args.confirmed !== true) {
|
|
3098
|
+
try {
|
|
3099
|
+
const post = await client.wpGetPost(args.post_id);
|
|
3100
|
+
const preview = `## Publish WordPress Post
|
|
3101
|
+
|
|
3102
|
+
**Post ID:** ${args.post_id}
|
|
3103
|
+
**Title:** ${post.title?.rendered ?? "(no title)"}
|
|
3104
|
+
**Current Status:** ${post.status ?? "unknown"}
|
|
3105
|
+
|
|
3106
|
+
This will publish this post on your WordPress site, making it publicly visible.
|
|
3107
|
+
|
|
3108
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3109
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3110
|
+
} catch {
|
|
3111
|
+
const preview = `## Publish WordPress Post
|
|
3112
|
+
|
|
3113
|
+
**Post ID:** ${args.post_id}
|
|
3114
|
+
|
|
3115
|
+
This will publish this post. Call this tool again with confirmed=true to proceed.`;
|
|
3116
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const result = await client.wpUpdatePost(args.post_id, {
|
|
3120
|
+
status: "publish"
|
|
3121
|
+
});
|
|
3122
|
+
return {
|
|
3123
|
+
content: [
|
|
3124
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3125
|
+
]
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
);
|
|
3129
|
+
server.tool(
|
|
3130
|
+
"wp_list_posts",
|
|
3131
|
+
"List WordPress posts with optional filters.",
|
|
3132
|
+
{
|
|
3133
|
+
status: z13.string().optional().describe(
|
|
3134
|
+
"Filter by status: draft, publish, pending, private, future, trash"
|
|
3135
|
+
),
|
|
3136
|
+
per_page: z13.string().optional().describe("Number of posts per page (default 10, max 100)"),
|
|
3137
|
+
page: z13.string().optional().describe("Page number for pagination"),
|
|
3138
|
+
search: z13.string().optional().describe("Search posts by keyword"),
|
|
3139
|
+
categories: z13.string().optional().describe("Filter by category ID (comma-separated)"),
|
|
3140
|
+
tags: z13.string().optional().describe("Filter by tag ID (comma-separated)")
|
|
3141
|
+
},
|
|
3142
|
+
{
|
|
3143
|
+
title: "List WordPress Posts",
|
|
3144
|
+
readOnlyHint: true,
|
|
3145
|
+
destructiveHint: false,
|
|
3146
|
+
idempotentHint: true,
|
|
3147
|
+
openWorldHint: true
|
|
3148
|
+
},
|
|
3149
|
+
async (args) => {
|
|
3150
|
+
const params = {};
|
|
3151
|
+
if (args.status) params.status = args.status;
|
|
3152
|
+
if (args.per_page) params.per_page = args.per_page;
|
|
3153
|
+
if (args.page) params.page = args.page;
|
|
3154
|
+
if (args.search) params.search = args.search;
|
|
3155
|
+
if (args.categories) params.categories = args.categories;
|
|
3156
|
+
if (args.tags) params.tags = args.tags;
|
|
3157
|
+
const result = await client.wpListPosts(params);
|
|
3158
|
+
return {
|
|
3159
|
+
content: [
|
|
3160
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3161
|
+
]
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
);
|
|
3165
|
+
server.tool(
|
|
3166
|
+
"wp_get_post",
|
|
3167
|
+
"Get detailed information about a specific WordPress post by ID.",
|
|
3168
|
+
{
|
|
3169
|
+
post_id: z13.number().describe("The WordPress post ID to retrieve")
|
|
3170
|
+
},
|
|
3171
|
+
{
|
|
3172
|
+
title: "Get WordPress Post",
|
|
3173
|
+
readOnlyHint: true,
|
|
3174
|
+
destructiveHint: false,
|
|
3175
|
+
idempotentHint: true,
|
|
3176
|
+
openWorldHint: true
|
|
3177
|
+
},
|
|
3178
|
+
async (args) => {
|
|
3179
|
+
const result = await client.wpGetPost(args.post_id);
|
|
3180
|
+
return {
|
|
3181
|
+
content: [
|
|
3182
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3183
|
+
]
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
);
|
|
3187
|
+
server.tool(
|
|
3188
|
+
"wp_delete_post",
|
|
3189
|
+
"Trash a WordPress post. Use force=true to permanently delete. Requires confirmation.",
|
|
3190
|
+
{
|
|
3191
|
+
post_id: z13.number().describe("The WordPress post ID to delete"),
|
|
3192
|
+
force: z13.boolean().default(false).describe("Set to true to permanently delete instead of trashing"),
|
|
3193
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3194
|
+
"Set to true to confirm deletion. If false, returns a preview."
|
|
3195
|
+
)
|
|
3196
|
+
},
|
|
3197
|
+
{
|
|
3198
|
+
title: "Delete WordPress Post",
|
|
3199
|
+
readOnlyHint: false,
|
|
3200
|
+
destructiveHint: true,
|
|
3201
|
+
idempotentHint: true,
|
|
3202
|
+
openWorldHint: true
|
|
3203
|
+
},
|
|
3204
|
+
async (args) => {
|
|
3205
|
+
if (args.confirmed !== true) {
|
|
3206
|
+
const action = args.force ? "permanently delete" : "move to trash";
|
|
3207
|
+
const preview = `## Delete WordPress Post
|
|
3208
|
+
|
|
3209
|
+
**Post ID:** ${args.post_id}
|
|
3210
|
+
**Action:** ${action}
|
|
3211
|
+
|
|
3212
|
+
${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.`;
|
|
3213
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3214
|
+
}
|
|
3215
|
+
const params = {};
|
|
3216
|
+
if (args.force) params.force = "true";
|
|
3217
|
+
const result = await client.wpDeletePost(args.post_id, params);
|
|
3218
|
+
return {
|
|
3219
|
+
content: [
|
|
3220
|
+
{
|
|
3221
|
+
type: "text",
|
|
3222
|
+
text: args.force ? "Post permanently deleted." : JSON.stringify(result, null, 2)
|
|
3223
|
+
}
|
|
3224
|
+
]
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
);
|
|
3228
|
+
server.tool(
|
|
3229
|
+
"wp_upload_media",
|
|
3230
|
+
"Upload an image from a URL to the WordPress media library.",
|
|
3231
|
+
{
|
|
3232
|
+
url: z13.string().url().describe("Public URL of the image to upload to WordPress"),
|
|
3233
|
+
filename: z13.string().optional().describe("Optional filename for the uploaded image")
|
|
3234
|
+
},
|
|
3235
|
+
{
|
|
3236
|
+
title: "Upload WordPress Media",
|
|
3237
|
+
readOnlyHint: false,
|
|
3238
|
+
destructiveHint: false,
|
|
3239
|
+
idempotentHint: false,
|
|
3240
|
+
openWorldHint: true
|
|
3241
|
+
},
|
|
3242
|
+
async (args) => {
|
|
3243
|
+
const result = await client.wpUploadMedia({
|
|
3244
|
+
url: args.url,
|
|
3245
|
+
filename: args.filename
|
|
3246
|
+
});
|
|
3247
|
+
return {
|
|
3248
|
+
content: [
|
|
3249
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3250
|
+
]
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
);
|
|
3254
|
+
server.tool(
|
|
3255
|
+
"wp_list_categories",
|
|
3256
|
+
"List all available WordPress categories.",
|
|
3257
|
+
{},
|
|
3258
|
+
{
|
|
3259
|
+
title: "List WordPress Categories",
|
|
3260
|
+
readOnlyHint: true,
|
|
3261
|
+
destructiveHint: false,
|
|
3262
|
+
idempotentHint: true,
|
|
3263
|
+
openWorldHint: true
|
|
3264
|
+
},
|
|
3265
|
+
async () => {
|
|
3266
|
+
const result = await client.wpListCategories();
|
|
3267
|
+
return {
|
|
3268
|
+
content: [
|
|
3269
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3270
|
+
]
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
);
|
|
3274
|
+
server.tool(
|
|
3275
|
+
"wp_list_tags",
|
|
3276
|
+
"List all available WordPress tags.",
|
|
3277
|
+
{},
|
|
3278
|
+
{
|
|
3279
|
+
title: "List WordPress Tags",
|
|
3280
|
+
readOnlyHint: true,
|
|
3281
|
+
destructiveHint: false,
|
|
3282
|
+
idempotentHint: true,
|
|
3283
|
+
openWorldHint: true
|
|
3284
|
+
},
|
|
3285
|
+
async () => {
|
|
3286
|
+
const result = await client.wpListTags();
|
|
3287
|
+
return {
|
|
3288
|
+
content: [
|
|
3289
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3290
|
+
]
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
server.tool(
|
|
3295
|
+
"wp_create_page",
|
|
3296
|
+
"Create a new WordPress page. Defaults to draft status.",
|
|
3297
|
+
{
|
|
3298
|
+
title: z13.string().optional().describe("Page title"),
|
|
3299
|
+
content: z13.string().optional().describe("Page content (HTML)"),
|
|
3300
|
+
status: z13.enum(["draft", "publish", "pending", "private"]).default("draft").describe("Page status"),
|
|
3301
|
+
excerpt: z13.string().optional().describe("Page excerpt"),
|
|
3302
|
+
slug: z13.string().optional().describe("URL slug for the page"),
|
|
3303
|
+
featured_media: z13.number().optional().describe("Featured image attachment ID"),
|
|
3304
|
+
confirmed: z13.boolean().default(false).describe(
|
|
3305
|
+
"Set to true to confirm creation. Required when status is 'publish'."
|
|
3306
|
+
)
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
title: "Create WordPress Page",
|
|
3310
|
+
readOnlyHint: false,
|
|
3311
|
+
destructiveHint: false,
|
|
3312
|
+
idempotentHint: false,
|
|
3313
|
+
openWorldHint: true
|
|
3314
|
+
},
|
|
3315
|
+
async (args) => {
|
|
3316
|
+
if (args.confirmed !== true && args.status === "publish") {
|
|
3317
|
+
const preview = `## WordPress Page Preview
|
|
3318
|
+
|
|
3319
|
+
**Title:** ${args.title ?? "(no title)"}
|
|
3320
|
+
**Status:** ${args.status}
|
|
3321
|
+
**Content length:** ${(args.content ?? "").length} characters
|
|
3322
|
+
` + (args.slug ? `**Slug:** ${args.slug}
|
|
3323
|
+
` : "") + `
|
|
3324
|
+
This will publish the page immediately on your WordPress site.
|
|
3325
|
+
|
|
3326
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3327
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3328
|
+
}
|
|
3329
|
+
const body = {
|
|
3330
|
+
title: args.title,
|
|
3331
|
+
content: args.content,
|
|
3332
|
+
status: args.status
|
|
3333
|
+
};
|
|
3334
|
+
if (args.excerpt) body.excerpt = args.excerpt;
|
|
3335
|
+
if (args.slug) body.slug = args.slug;
|
|
3336
|
+
if (args.featured_media) body.featured_media = args.featured_media;
|
|
3337
|
+
const result = await client.wpCreatePage(body);
|
|
3338
|
+
return {
|
|
3339
|
+
content: [
|
|
3340
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3341
|
+
]
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
);
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// src/tools/ghost.ts
|
|
3348
|
+
import { z as z14 } from "zod";
|
|
3349
|
+
function registerGhostTools(server, client) {
|
|
3350
|
+
server.tool(
|
|
3351
|
+
"ghost_create_post",
|
|
3352
|
+
"Create a post on Ghost. Defaults to draft status. Set confirmed=false to preview before publishing.",
|
|
3353
|
+
{
|
|
3354
|
+
title: z14.string().describe("Post title"),
|
|
3355
|
+
html: z14.string().optional().describe("HTML content of the post"),
|
|
3356
|
+
status: z14.enum(["draft", "published", "scheduled"]).default("draft").describe("Post status: draft, published, or scheduled"),
|
|
3357
|
+
tags: z14.array(z14.string()).optional().describe("Tag names to apply to the post"),
|
|
3358
|
+
featured: z14.boolean().optional().describe("Whether the post is featured"),
|
|
3359
|
+
custom_excerpt: z14.string().optional().describe("Custom excerpt for the post"),
|
|
3360
|
+
feature_image: z14.string().optional().describe("URL of the feature image"),
|
|
3361
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3362
|
+
"Set to true to confirm creation. Required when status is 'published'. Drafts are created immediately."
|
|
3363
|
+
)
|
|
3364
|
+
},
|
|
3365
|
+
{
|
|
3366
|
+
title: "Create Ghost Post",
|
|
3367
|
+
readOnlyHint: false,
|
|
3368
|
+
destructiveHint: false,
|
|
3369
|
+
idempotentHint: false,
|
|
3370
|
+
openWorldHint: true
|
|
3371
|
+
},
|
|
3372
|
+
async (args) => {
|
|
3373
|
+
const needsConfirmation = args.status !== "draft";
|
|
3374
|
+
if (needsConfirmation && args.confirmed !== true) {
|
|
3375
|
+
const preview = `## Ghost Post Preview
|
|
3376
|
+
|
|
3377
|
+
**Title:** ${args.title}
|
|
3378
|
+
**Status:** ${args.status}
|
|
3379
|
+
` + (args.tags?.length ? `**Tags:** ${args.tags.join(", ")}
|
|
3380
|
+
` : "") + (args.featured ? `**Featured:** yes
|
|
3381
|
+
` : "") + (args.custom_excerpt ? `**Excerpt:** ${args.custom_excerpt}
|
|
3382
|
+
` : "") + (args.feature_image ? `**Feature Image:** ${args.feature_image}
|
|
3383
|
+
` : "") + (args.html ? `
|
|
3384
|
+
**Content preview:** ${args.html.replace(/<[^>]*>/g, "").slice(0, 200)}...
|
|
3385
|
+
` : "") + `
|
|
3386
|
+
**Action:** This will be **${args.status}** on Ghost immediately.
|
|
3387
|
+
|
|
3388
|
+
Call this tool again with confirmed=true to proceed.`;
|
|
3389
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3390
|
+
}
|
|
3391
|
+
const body = {
|
|
3392
|
+
title: args.title,
|
|
3393
|
+
status: args.status
|
|
3394
|
+
};
|
|
3395
|
+
if (args.html) body.html = args.html;
|
|
3396
|
+
if (args.tags) body.tags = args.tags.map((name) => ({ name }));
|
|
3397
|
+
if (args.featured !== void 0) body.featured = args.featured;
|
|
3398
|
+
if (args.custom_excerpt) body.custom_excerpt = args.custom_excerpt;
|
|
3399
|
+
if (args.feature_image) body.feature_image = args.feature_image;
|
|
3400
|
+
const result = await client.ghostCreatePost(body);
|
|
3401
|
+
return {
|
|
3402
|
+
content: [
|
|
3403
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3404
|
+
]
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
);
|
|
3408
|
+
server.tool(
|
|
3409
|
+
"ghost_update_post",
|
|
3410
|
+
"Update an existing Ghost post. Requires updated_at for collision detection.",
|
|
3411
|
+
{
|
|
3412
|
+
post_id: z14.string().describe("The Ghost post ID to update"),
|
|
3413
|
+
updated_at: z14.string().describe(
|
|
3414
|
+
"The updated_at value from the post (required for collision detection)"
|
|
3415
|
+
),
|
|
3416
|
+
title: z14.string().optional().describe("Updated post title"),
|
|
3417
|
+
html: z14.string().optional().describe("Updated HTML content"),
|
|
3418
|
+
status: z14.enum(["draft", "published", "scheduled"]).optional().describe("Updated status"),
|
|
3419
|
+
tags: z14.array(z14.string()).optional().describe("Updated tag names"),
|
|
3420
|
+
featured: z14.boolean().optional().describe("Whether the post is featured"),
|
|
3421
|
+
custom_excerpt: z14.string().optional().describe("Updated custom excerpt"),
|
|
3422
|
+
feature_image: z14.string().optional().describe("Updated feature image URL")
|
|
3423
|
+
},
|
|
3424
|
+
{
|
|
3425
|
+
title: "Update Ghost Post",
|
|
3426
|
+
readOnlyHint: false,
|
|
3427
|
+
destructiveHint: false,
|
|
3428
|
+
idempotentHint: true,
|
|
3429
|
+
openWorldHint: true
|
|
3430
|
+
},
|
|
3431
|
+
async (args) => {
|
|
3432
|
+
const data = {
|
|
3433
|
+
updated_at: args.updated_at
|
|
3434
|
+
};
|
|
3435
|
+
if (args.title) data.title = args.title;
|
|
3436
|
+
if (args.html) data.html = args.html;
|
|
3437
|
+
if (args.status) data.status = args.status;
|
|
3438
|
+
if (args.tags) data.tags = args.tags.map((name) => ({ name }));
|
|
3439
|
+
if (args.featured !== void 0) data.featured = args.featured;
|
|
3440
|
+
if (args.custom_excerpt) data.custom_excerpt = args.custom_excerpt;
|
|
3441
|
+
if (args.feature_image) data.feature_image = args.feature_image;
|
|
3442
|
+
const result = await client.ghostUpdatePost(args.post_id, data);
|
|
3443
|
+
return {
|
|
3444
|
+
content: [
|
|
3445
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
3446
|
+
]
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
);
|
|
3450
|
+
server.tool(
|
|
3451
|
+
"ghost_publish_post",
|
|
3452
|
+
"Publish a Ghost draft post. This makes the post live. Set confirmed=false to preview first.",
|
|
3453
|
+
{
|
|
3454
|
+
post_id: z14.string().describe("The Ghost post ID to publish"),
|
|
3455
|
+
updated_at: z14.string().describe(
|
|
3456
|
+
"The updated_at value from the post (required for collision detection)"
|
|
3457
|
+
),
|
|
3458
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3459
|
+
"Set to true to confirm publishing. If false or missing, returns a confirmation prompt."
|
|
3460
|
+
)
|
|
3461
|
+
},
|
|
3462
|
+
{
|
|
3463
|
+
title: "Publish Ghost Post",
|
|
3464
|
+
readOnlyHint: false,
|
|
3465
|
+
destructiveHint: false,
|
|
3466
|
+
idempotentHint: false,
|
|
3467
|
+
openWorldHint: true
|
|
3468
|
+
},
|
|
3469
|
+
async (args) => {
|
|
3470
|
+
if (args.confirmed !== true) {
|
|
3471
|
+
let postInfo = "";
|
|
3472
|
+
try {
|
|
3473
|
+
const post = await client.ghostGetPost(args.post_id);
|
|
3474
|
+
postInfo = `**Title:** ${post?.title ?? "Unknown"}
|
|
3475
|
+
**Current Status:** ${post?.status ?? "Unknown"}
|
|
3476
|
+
` + (post?.tags?.length ? `**Tags:** ${post.tags.map((t) => t.name).join(", ")}
|
|
3477
|
+
` : "");
|
|
3478
|
+
} catch {
|
|
3479
|
+
postInfo = `**Post ID:** ${args.post_id}
|
|
3480
|
+
`;
|
|
3481
|
+
}
|
|
3482
|
+
const preview = `## Publish Ghost Post Confirmation
|
|
3483
|
+
|
|
3484
|
+
` + postInfo + `
|
|
3485
|
+
**Action:** This will make the post **live** on your Ghost site.
|
|
3486
|
+
|
|
3487
|
+
Call this tool again with confirmed=true to publish.`;
|
|
3488
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3489
|
+
}
|
|
3490
|
+
const result = await client.ghostPublishPost(args.post_id, {
|
|
3491
|
+
updated_at: args.updated_at
|
|
3492
|
+
});
|
|
3493
|
+
return {
|
|
3494
|
+
content: [
|
|
3495
|
+
{ type: "text", text: `Post published successfully.
|
|
3496
|
+
|
|
3497
|
+
${JSON.stringify(result, null, 2)}` }
|
|
3498
|
+
]
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
);
|
|
3502
|
+
server.tool(
|
|
3503
|
+
"ghost_list_posts",
|
|
3504
|
+
"List posts from Ghost with optional status filter.",
|
|
3505
|
+
{
|
|
3506
|
+
status: z14.enum(["draft", "published", "scheduled"]).optional().describe("Filter posts by status"),
|
|
3507
|
+
limit: z14.string().optional().describe("Number of posts to return (default: 15)"),
|
|
3508
|
+
page: z14.string().optional().describe("Page number for pagination")
|
|
3509
|
+
},
|
|
3510
|
+
{
|
|
3511
|
+
title: "List Ghost Posts",
|
|
3512
|
+
readOnlyHint: true,
|
|
3513
|
+
destructiveHint: false,
|
|
3514
|
+
idempotentHint: true,
|
|
3515
|
+
openWorldHint: true
|
|
3516
|
+
},
|
|
3517
|
+
async (args) => {
|
|
3518
|
+
const params = {};
|
|
3519
|
+
if (args.status) params.status = args.status;
|
|
3520
|
+
if (args.limit) params.limit = args.limit;
|
|
3521
|
+
if (args.page) params.page = args.page;
|
|
3522
|
+
const result = await client.ghostListPosts(params);
|
|
3523
|
+
const posts = result?.posts ?? [];
|
|
3524
|
+
if (posts.length === 0) {
|
|
3525
|
+
return {
|
|
3526
|
+
content: [
|
|
3527
|
+
{
|
|
3528
|
+
type: "text",
|
|
3529
|
+
text: "No Ghost posts found matching your filters."
|
|
3530
|
+
}
|
|
3531
|
+
]
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
let text = `## Ghost Posts (${posts.length}`;
|
|
3535
|
+
if (result?.meta?.pagination?.total != null) {
|
|
3536
|
+
text += ` of ${result.meta.pagination.total}`;
|
|
3537
|
+
}
|
|
3538
|
+
text += ")\n\n";
|
|
3539
|
+
for (const p of posts) {
|
|
3540
|
+
const status = (p.status ?? "unknown").toUpperCase();
|
|
3541
|
+
const date = p.published_at ? new Date(p.published_at).toLocaleString() : p.created_at ? new Date(p.created_at).toLocaleString() : "";
|
|
3542
|
+
const tags = p.tags?.length > 0 ? ` | Tags: ${p.tags.map((t) => t.name).join(", ")}` : "";
|
|
3543
|
+
text += `- **[${status}]** "${p.title ?? "(no title)"}"
|
|
3544
|
+
`;
|
|
3545
|
+
text += ` ID: \`${p.id}\``;
|
|
3546
|
+
if (date) text += ` | ${p.published_at ? "Published" : "Created"}: ${date}`;
|
|
3547
|
+
text += tags;
|
|
3548
|
+
text += "\n";
|
|
3549
|
+
}
|
|
3550
|
+
return { content: [{ type: "text", text }] };
|
|
3551
|
+
}
|
|
3552
|
+
);
|
|
3553
|
+
server.tool(
|
|
3554
|
+
"ghost_get_post",
|
|
3555
|
+
"Get detailed information about a specific Ghost post by ID.",
|
|
3556
|
+
{
|
|
3557
|
+
post_id: z14.string().describe("The Ghost post ID to retrieve")
|
|
3558
|
+
},
|
|
3559
|
+
{
|
|
3560
|
+
title: "Get Ghost Post",
|
|
3561
|
+
readOnlyHint: true,
|
|
3562
|
+
destructiveHint: false,
|
|
3563
|
+
idempotentHint: true,
|
|
3564
|
+
openWorldHint: true
|
|
3565
|
+
},
|
|
3566
|
+
async (args) => {
|
|
3567
|
+
const result = await client.ghostGetPost(args.post_id);
|
|
3568
|
+
let text = `## Ghost Post Details
|
|
3569
|
+
|
|
3570
|
+
`;
|
|
3571
|
+
text += `**Title:** ${result?.title ?? "Unknown"}
|
|
3572
|
+
`;
|
|
3573
|
+
text += `**ID:** \`${result?.id}\`
|
|
3574
|
+
`;
|
|
3575
|
+
text += `**Status:** ${result?.status ?? "Unknown"}
|
|
3576
|
+
`;
|
|
3577
|
+
text += `**Slug:** ${result?.slug ?? "N/A"}
|
|
3578
|
+
`;
|
|
3579
|
+
if (result?.url) text += `**URL:** ${result.url}
|
|
3580
|
+
`;
|
|
3581
|
+
if (result?.custom_excerpt) text += `**Excerpt:** ${result.custom_excerpt}
|
|
3582
|
+
`;
|
|
3583
|
+
if (result?.feature_image) text += `**Feature Image:** ${result.feature_image}
|
|
3584
|
+
`;
|
|
3585
|
+
if (result?.featured) text += `**Featured:** yes
|
|
3586
|
+
`;
|
|
3587
|
+
if (result?.tags?.length > 0) {
|
|
3588
|
+
text += `**Tags:** ${result.tags.map((t) => t.name).join(", ")}
|
|
3589
|
+
`;
|
|
3590
|
+
}
|
|
3591
|
+
if (result?.authors?.length > 0) {
|
|
3592
|
+
text += `**Authors:** ${result.authors.map((a) => a.name).join(", ")}
|
|
3593
|
+
`;
|
|
3594
|
+
}
|
|
3595
|
+
if (result?.published_at) {
|
|
3596
|
+
text += `**Published:** ${new Date(result.published_at).toLocaleString()}
|
|
3597
|
+
`;
|
|
3598
|
+
}
|
|
3599
|
+
text += `**Created:** ${result?.created_at ? new Date(result.created_at).toLocaleString() : "N/A"}
|
|
3600
|
+
`;
|
|
3601
|
+
text += `**Updated:** ${result?.updated_at ?? "N/A"}
|
|
3602
|
+
`;
|
|
3603
|
+
text += `
|
|
3604
|
+
*Use updated_at value \`${result?.updated_at}\` when updating or publishing this post.*
|
|
3605
|
+
`;
|
|
3606
|
+
if (result?.html) {
|
|
3607
|
+
const plainText = result.html.replace(/<[^>]*>/g, "");
|
|
3608
|
+
const preview = plainText.length > 500 ? plainText.slice(0, 500) + "..." : plainText;
|
|
3609
|
+
text += `
|
|
3610
|
+
### Content Preview
|
|
3611
|
+
${preview}
|
|
3612
|
+
`;
|
|
3613
|
+
}
|
|
3614
|
+
return { content: [{ type: "text", text }] };
|
|
3615
|
+
}
|
|
3616
|
+
);
|
|
3617
|
+
server.tool(
|
|
3618
|
+
"ghost_send_newsletter",
|
|
3619
|
+
"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.",
|
|
3620
|
+
{
|
|
3621
|
+
post_id: z14.string().describe("The Ghost post ID to send as newsletter"),
|
|
3622
|
+
updated_at: z14.string().describe("The updated_at value from the post"),
|
|
3623
|
+
newsletter_id: z14.string().describe(
|
|
3624
|
+
"The Ghost newsletter ID to send through. Use ghost_list_newsletters to find available newsletters."
|
|
3625
|
+
),
|
|
3626
|
+
email_only: z14.boolean().default(false).describe(
|
|
3627
|
+
"If true, sends email only without publishing the post on the site"
|
|
3628
|
+
),
|
|
3629
|
+
confirmed: z14.boolean().default(false).describe(
|
|
3630
|
+
"Set to true to confirm and send. If false or missing, returns a confirmation prompt."
|
|
3631
|
+
)
|
|
3632
|
+
},
|
|
3633
|
+
{
|
|
3634
|
+
title: "Send Ghost Newsletter",
|
|
3635
|
+
readOnlyHint: false,
|
|
3636
|
+
destructiveHint: false,
|
|
3637
|
+
idempotentHint: false,
|
|
3638
|
+
openWorldHint: true
|
|
3639
|
+
},
|
|
3640
|
+
async (args) => {
|
|
3641
|
+
if (args.confirmed !== true) {
|
|
3642
|
+
let postInfo = "";
|
|
3643
|
+
let memberCount = "unknown";
|
|
3644
|
+
let newsletterName = "unknown";
|
|
3645
|
+
try {
|
|
3646
|
+
const post = await client.ghostGetPost(args.post_id);
|
|
3647
|
+
postInfo = `**Title:** ${post?.title ?? "Unknown"}
|
|
3648
|
+
**Current Status:** ${post?.status ?? "Unknown"}
|
|
3649
|
+
`;
|
|
3650
|
+
} catch {
|
|
3651
|
+
postInfo = `**Post ID:** ${args.post_id}
|
|
3652
|
+
`;
|
|
3653
|
+
}
|
|
3654
|
+
try {
|
|
3655
|
+
const members = await client.ghostListMembers({ limit: "1" });
|
|
3656
|
+
memberCount = String(
|
|
3657
|
+
members?.meta?.pagination?.total ?? "unknown"
|
|
3658
|
+
);
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
try {
|
|
3662
|
+
const newsletters = await client.ghostListNewsletters();
|
|
3663
|
+
const nl = (newsletters?.newsletters ?? []).find(
|
|
3664
|
+
(n) => n.id === args.newsletter_id
|
|
3665
|
+
);
|
|
3666
|
+
if (nl) newsletterName = nl.name;
|
|
3667
|
+
} catch {
|
|
3668
|
+
}
|
|
3669
|
+
const preview = `## Send Ghost Newsletter Confirmation
|
|
3670
|
+
|
|
3671
|
+
` + postInfo + `**Newsletter:** ${newsletterName} (\`${args.newsletter_id}\`)
|
|
3672
|
+
**Members:** ~${memberCount} subscribers
|
|
3673
|
+
**Email Only:** ${args.email_only ? "yes (post will NOT be published on site)" : "no (post will also be published)"}
|
|
3674
|
+
|
|
3675
|
+
**This action cannot be undone.** Once sent, the email goes to all newsletter subscribers.
|
|
3676
|
+
|
|
3677
|
+
Call this tool again with confirmed=true to send.`;
|
|
3678
|
+
return { content: [{ type: "text", text: preview }] };
|
|
3679
|
+
}
|
|
3680
|
+
const result = await client.ghostSendNewsletter(args.post_id, {
|
|
3681
|
+
updated_at: args.updated_at,
|
|
3682
|
+
newsletter_id: args.newsletter_id,
|
|
3683
|
+
email_only: args.email_only
|
|
3684
|
+
});
|
|
3685
|
+
return {
|
|
3686
|
+
content: [
|
|
3687
|
+
{
|
|
3688
|
+
type: "text",
|
|
3689
|
+
text: `Newsletter sent successfully.
|
|
3690
|
+
|
|
3691
|
+
${JSON.stringify(result, null, 2)}`
|
|
3692
|
+
}
|
|
3693
|
+
]
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
);
|
|
3697
|
+
server.tool(
|
|
3698
|
+
"ghost_list_members",
|
|
3699
|
+
"List Ghost newsletter members (subscribers).",
|
|
3700
|
+
{
|
|
3701
|
+
limit: z14.string().optional().describe("Number of members to return (default: 15)"),
|
|
3702
|
+
page: z14.string().optional().describe("Page number for pagination"),
|
|
3703
|
+
filter: z14.string().optional().describe(
|
|
3704
|
+
"Ghost NQL filter string, e.g. 'status:free' or 'subscribed:true'"
|
|
3705
|
+
)
|
|
3706
|
+
},
|
|
3707
|
+
{
|
|
3708
|
+
title: "List Ghost Members",
|
|
3709
|
+
readOnlyHint: true,
|
|
3710
|
+
destructiveHint: false,
|
|
3711
|
+
idempotentHint: true,
|
|
3712
|
+
openWorldHint: true
|
|
3713
|
+
},
|
|
3714
|
+
async (args) => {
|
|
3715
|
+
const params = {};
|
|
3716
|
+
if (args.limit) params.limit = args.limit;
|
|
3717
|
+
if (args.page) params.page = args.page;
|
|
3718
|
+
if (args.filter) params.filter = args.filter;
|
|
3719
|
+
const result = await client.ghostListMembers(params);
|
|
3720
|
+
const members = result?.members ?? [];
|
|
3721
|
+
if (members.length === 0) {
|
|
3722
|
+
return {
|
|
3723
|
+
content: [
|
|
3724
|
+
{
|
|
3725
|
+
type: "text",
|
|
3726
|
+
text: "No Ghost members found matching your filters."
|
|
3727
|
+
}
|
|
3728
|
+
]
|
|
3729
|
+
};
|
|
3730
|
+
}
|
|
3731
|
+
let text = `## Ghost Members (${members.length}`;
|
|
3732
|
+
if (result?.meta?.pagination?.total != null) {
|
|
3733
|
+
text += ` of ${result.meta.pagination.total}`;
|
|
3734
|
+
}
|
|
3735
|
+
text += ")\n\n";
|
|
3736
|
+
for (const m of members) {
|
|
3737
|
+
const status = m.status ?? "unknown";
|
|
3738
|
+
text += `- **${m.name || m.email}**`;
|
|
3739
|
+
if (m.name && m.email) text += ` (${m.email})`;
|
|
3740
|
+
text += ` | Status: ${status}`;
|
|
3741
|
+
if (m.created_at) {
|
|
3742
|
+
text += ` | Joined: ${new Date(m.created_at).toLocaleDateString()}`;
|
|
3743
|
+
}
|
|
3744
|
+
text += "\n";
|
|
3745
|
+
}
|
|
3746
|
+
return { content: [{ type: "text", text }] };
|
|
3747
|
+
}
|
|
3748
|
+
);
|
|
3749
|
+
server.tool(
|
|
3750
|
+
"ghost_list_newsletters",
|
|
3751
|
+
"List configured newsletters in Ghost.",
|
|
3752
|
+
{},
|
|
3753
|
+
{
|
|
3754
|
+
title: "List Ghost Newsletters",
|
|
3755
|
+
readOnlyHint: true,
|
|
3756
|
+
destructiveHint: false,
|
|
3757
|
+
idempotentHint: true,
|
|
3758
|
+
openWorldHint: true
|
|
3759
|
+
},
|
|
3760
|
+
async () => {
|
|
3761
|
+
const result = await client.ghostListNewsletters();
|
|
3762
|
+
const newsletters = result?.newsletters ?? [];
|
|
3763
|
+
if (newsletters.length === 0) {
|
|
3764
|
+
return {
|
|
3765
|
+
content: [
|
|
3766
|
+
{
|
|
3767
|
+
type: "text",
|
|
3768
|
+
text: "No newsletters configured in Ghost."
|
|
3769
|
+
}
|
|
3770
|
+
]
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
let text = `## Ghost Newsletters (${newsletters.length})
|
|
3774
|
+
|
|
3775
|
+
`;
|
|
3776
|
+
for (const nl of newsletters) {
|
|
3777
|
+
text += `- **${nl.name}**
|
|
3778
|
+
`;
|
|
3779
|
+
text += ` ID: \`${nl.id}\``;
|
|
3780
|
+
if (nl.description) text += ` | ${nl.description}`;
|
|
3781
|
+
text += `
|
|
3782
|
+
Status: ${nl.status ?? "active"}`;
|
|
3783
|
+
if (nl.subscribe_on_signup !== void 0) {
|
|
3784
|
+
text += ` | Subscribe on signup: ${nl.subscribe_on_signup ? "yes" : "no"}`;
|
|
3785
|
+
}
|
|
3786
|
+
if (nl.member_count != null) {
|
|
3787
|
+
text += ` | Members: ${nl.member_count}`;
|
|
3788
|
+
}
|
|
3789
|
+
text += "\n";
|
|
3790
|
+
}
|
|
3791
|
+
text += `
|
|
3792
|
+
*Use the newsletter ID when sending a post as a newsletter with ghost_send_newsletter.*
|
|
3793
|
+
`;
|
|
3794
|
+
return { content: [{ type: "text", text }] };
|
|
3795
|
+
}
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3313
3798
|
export {
|
|
3314
3799
|
BuzzPosterClient,
|
|
3315
3800
|
registerAccountInfoTool,
|
|
@@ -3318,15 +3803,15 @@ export {
|
|
|
3318
3803
|
registerAudienceTools,
|
|
3319
3804
|
registerBrandVoiceTools,
|
|
3320
3805
|
registerCalendarTools,
|
|
3806
|
+
registerGhostTools,
|
|
3321
3807
|
registerInboxTools,
|
|
3322
|
-
registerKnowledgeTools,
|
|
3323
3808
|
registerMediaTools,
|
|
3324
3809
|
registerNewsletterAdvancedTools,
|
|
3325
3810
|
registerNewsletterTemplateTools,
|
|
3326
3811
|
registerNewsletterTools,
|
|
3327
3812
|
registerNotificationTools,
|
|
3328
3813
|
registerPostTools,
|
|
3329
|
-
registerPublishingRulesTools,
|
|
3330
3814
|
registerRssTools,
|
|
3331
|
-
registerSourceTools
|
|
3815
|
+
registerSourceTools,
|
|
3816
|
+
registerWordPressTools
|
|
3332
3817
|
};
|