@buzzposter/mcp 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +96 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -101,12 +101,28 @@ var BuzzPosterClient = class {
101
101
  });
102
102
  }
103
103
  // Media
104
- async uploadMedia(filename, base64Data, mimeType) {
105
- return this.request("POST", "/api/v1/media/upload-base64", {
106
- filename,
107
- data: base64Data,
108
- mimeType
104
+ async uploadMediaMultipart(filename, buffer, mimeType) {
105
+ const formData = new FormData();
106
+ formData.append("file", new Blob([buffer], { type: mimeType }), filename);
107
+ const url = new URL(`${this.baseUrl}/api/v1/media/upload`);
108
+ const res = await fetch(url, {
109
+ method: "POST",
110
+ headers: { Authorization: `Bearer ${this.apiKey}` },
111
+ body: formData
109
112
  });
113
+ if (!res.ok) {
114
+ const errorBody = await res.json().catch(() => ({ message: res.statusText }));
115
+ const message = typeof errorBody === "object" && errorBody !== null && "message" in errorBody ? String(errorBody.message) : `API error (${res.status})`;
116
+ throw new Error(`BuzzPoster API error (${res.status}): ${message}`);
117
+ }
118
+ const text = await res.text();
119
+ return text ? JSON.parse(text) : void 0;
120
+ }
121
+ async uploadFromUrl(data) {
122
+ return this.request("POST", "/api/v1/media/upload-from-url", data);
123
+ }
124
+ async getUploadUrl(data) {
125
+ return this.request("POST", "/api/v1/media/presign", data);
110
126
  }
111
127
  async listMedia() {
112
128
  return this.request("GET", "/api/v1/media");
@@ -540,7 +556,7 @@ Retrying will only attempt the failed platforms. Call this tool again with confi
540
556
  function registerAccountTools(server2, client2) {
541
557
  server2.tool(
542
558
  "list_accounts",
543
- "List all connected social media accounts with their platform, username, and connection status.",
559
+ "List all connected accounts \u2014 both social media platforms (Twitter, Instagram, etc.) and email service provider / ESP (Kit, Beehiiv, Mailchimp). Use this whenever the user asks about their accounts, connections, or integrations.",
544
560
  {},
545
561
  {
546
562
  title: "List Connected Accounts",
@@ -551,8 +567,44 @@ function registerAccountTools(server2, client2) {
551
567
  },
552
568
  async () => {
553
569
  const result = await client2.listAccounts();
570
+ const accounts = result.accounts ?? [];
571
+ const esp = result.esp;
572
+ if (accounts.length === 0 && !esp) {
573
+ return {
574
+ content: [{
575
+ type: "text",
576
+ text: "## Connected Accounts\n\nNo accounts connected. Connect social media accounts and/or an email service provider via the dashboard."
577
+ }]
578
+ };
579
+ }
580
+ let text = "## Connected Accounts\n\n";
581
+ text += "### Social Media\n";
582
+ if (accounts.length > 0) {
583
+ for (const a of accounts) {
584
+ const icon = a.isActive ? "\u2705" : "\u274C";
585
+ const platform = a.platform.charAt(0).toUpperCase() + a.platform.slice(1);
586
+ const name = a.username || a.displayName || a.platform;
587
+ text += `${icon} **${platform}** \u2014 @${name}
588
+ `;
589
+ }
590
+ } else {
591
+ text += "No social accounts connected.\n";
592
+ }
593
+ text += "\n### Email Service Provider (ESP)\n";
594
+ if (esp && esp.connected) {
595
+ const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
596
+ text += `\u2705 **${provider}** \u2014 Connected`;
597
+ if (esp.publicationId) text += ` (Publication: ${esp.publicationId})`;
598
+ text += "\n";
599
+ } else if (esp) {
600
+ const provider = esp.provider.charAt(0).toUpperCase() + esp.provider.slice(1);
601
+ text += `\u26A0\uFE0F **${provider}** \u2014 Provider set but API key missing
602
+ `;
603
+ } else {
604
+ text += "No ESP configured.\n";
605
+ }
554
606
  return {
555
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
607
+ content: [{ type: "text", text }]
556
608
  };
557
609
  }
558
610
  );
@@ -844,8 +896,7 @@ function registerMediaTools(server2, client2) {
844
896
  openWorldHint: false
845
897
  },
846
898
  async (args) => {
847
- const buffer = await readFile(args.file_path);
848
- const base64 = buffer.toString("base64");
899
+ const buffer = Buffer.from(await readFile(args.file_path));
849
900
  const filename = args.file_path.split("/").pop() ?? "upload";
850
901
  const ext = filename.split(".").pop()?.toLowerCase() ?? "";
851
902
  const mimeMap = {
@@ -861,33 +912,57 @@ function registerMediaTools(server2, client2) {
861
912
  pdf: "application/pdf"
862
913
  };
863
914
  const mimeType = mimeMap[ext] ?? "application/octet-stream";
864
- const result = await client2.uploadMedia(filename, base64, mimeType);
915
+ const result = await client2.uploadMediaMultipart(filename, buffer, mimeType);
865
916
  return {
866
917
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
867
918
  };
868
919
  }
869
920
  );
870
921
  server2.tool(
871
- "upload_media_base64",
872
- "Upload media from base64-encoded data. Returns a CDN URL that can be used in posts.",
922
+ "upload_from_url",
923
+ "Upload media from a public URL. The server fetches the image/video and uploads it to storage. Returns a CDN URL that can be used in posts. Supports JPEG, PNG, GIF, WebP, MP4, MOV, WebM up to 25MB.",
873
924
  {
874
- filename: z4.string().describe("The filename including extension"),
875
- data: z4.string().describe("Base64-encoded file data"),
876
- mime_type: z4.string().describe("MIME type of the file, e.g. image/jpeg, video/mp4")
925
+ url: z4.string().url().describe("Public URL of the image or video to upload"),
926
+ filename: z4.string().optional().describe("Optional filename override (including extension)"),
927
+ folder: z4.string().optional().describe("Optional folder path within the customer's storage")
877
928
  },
878
929
  {
879
- title: "Upload Media Base64",
930
+ title: "Upload Media from URL",
880
931
  readOnlyHint: false,
881
932
  destructiveHint: false,
882
933
  idempotentHint: false,
883
934
  openWorldHint: false
884
935
  },
885
936
  async (args) => {
886
- const result = await client2.uploadMedia(
887
- args.filename,
888
- args.data,
889
- args.mime_type
890
- );
937
+ const result = await client2.uploadFromUrl({
938
+ url: args.url,
939
+ filename: args.filename,
940
+ folder: args.folder
941
+ });
942
+ return {
943
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
944
+ };
945
+ }
946
+ );
947
+ server2.tool(
948
+ "get_upload_url",
949
+ "Get a pre-signed URL for direct client upload to storage. The URL is valid for 5 minutes. After uploading to the URL, the file will be available at the returned CDN URL.",
950
+ {
951
+ filename: z4.string().describe("The filename including extension (e.g. photo.jpg)"),
952
+ content_type: z4.string().describe("MIME type of the file (e.g. image/jpeg, video/mp4)")
953
+ },
954
+ {
955
+ title: "Get Upload URL",
956
+ readOnlyHint: true,
957
+ destructiveHint: false,
958
+ idempotentHint: false,
959
+ openWorldHint: false
960
+ },
961
+ async (args) => {
962
+ const result = await client2.getUploadUrl({
963
+ filename: args.filename,
964
+ content_type: args.content_type
965
+ });
891
966
  return {
892
967
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
893
968
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buzzposter/mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "BuzzPoster MCP server - Social media, newsletters, and media hosting for Claude Desktop",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",